扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
首先说说索引的 优点 :最大的好处无疑就是提高查询效率。有的索引还能保证数据的唯一性,比如唯一索引。
专注于为中小企业提供成都网站设计、成都网站制作、外贸网站建设服务,电脑端+手机端+微信端的三站合一,更高效的管理,为中小企业太子河免费做网站提供优质的服务。我们立足成都,凝聚了一批互联网行业人才,有力地推动了1000+企业的稳健成长,帮助中小企业通过网站建设实现规模扩充和转变。
而它的 坏处 也很明显:索引也是文件,我们在创建索引时,也会创建额外的文件,所以会占用一些硬盘空间。其次,索引也需要维护,我们在增加删除数据的时候,索引也需要去变化维护。当一个表的索引多了以后,资源消耗是很大的,所以必须结合实际业务再去确定给哪些列加索引。
再说说索引的基本结构。一说到这里肯定会脱口而出:B+树!了解B+树前先要了解二叉查找树和二叉平衡树。 二叉查找树 :左节点比父节点小,右节点比父节点大,所以二叉查找树的中序遍历就是树的各个节点从小到大的排序。 二叉平衡树 :左右子树高度差不能大于1。B+树就是结合了它们的特点,当然,不一定是二叉树。
为什么要有二叉查找树的特点?? 因为查找效率快,二分查找在这种结构下,查找效率是很快的。 那为什么要有平衡树的特点呢? 试想,如果不维护一颗树的平衡性,当插入一些数据后,树的形态有可能变得很极端,比如左子树一个数据没有,而全在右子树上,这种情况下,二分查找和遍历有什么区别呢?而就是因为这些特点需要去维护,所以就有了上面提到的缺点,当索引很多后,反而增加了系统的负担。
接着说B+树。 它的结构如下 :
可以发现,叶子节点其实是一个 双向循环链表 ,这种结构的好处就是,在范围查询的时候,我只用找到一个数据,就可以直接返回剩余的数据了。比如找小于30的,只用找到30,其余的直接通过叶子节点间的指针就可以找到。再说说其他特点: 数据只存在于叶子节点 。当叶子节点满了,如果再添加数据,就会拆分叶子节点,父节点就多了个子节点。如果父节点的位置也满了,就会扩充高度,就是拆分父节点,如25 50 75拆分成:25为左子树,75为右子树,50变成新的头节点,此时B+树的高度变成了3。它们的扩充的规律如下表,Leaf Page是叶子节点,index Page是非叶子节点。
再说说B树 ,B树相比较B+树,它所有节点都存放数据,所以在查找数据时,B树有可能没到达叶子节点就结束了。再者,B树的叶子节点间不存在指针。
最后说说Hash索引 ,相较于B+树,Hash索引最大的优点就是查找数据快。但是Hash索引最大的问题就是不支持范围查询。试想,如果查询小于30的数据,hash函数是根据数据的值找到其对应的位置,谁又知道小于30的有哪几个数据。而B+树正好相反,范围查询是它的强项。
附录: Hash到底是啥?? 哈希中文名散列,哈希只是它的音译。 为啥都说Hash快?? 首先有一块哈希表(散列表),它的数据结构是个数组,一个任意长度的数据通过hash函数都可以变成一个固定长度的数据,叫hash值。然后通过hash值确定在数组中的位置,相同数据的hash值是相同的,所以我们存储一个数据以后,只需O(1)的时间复杂度就可以找到数据。 那hash函数又是啥?? 算术运算或位运算,很多应用里都有hash函数,但实际运算过程大不一样。这是Java里String的hashCode方法:
publicint hashCode() {
}
还有一个问题,hash函数计算出来的hash值有可能存在碰撞,即两个不同的数据可能存在相同的hash值,在MySQL或其他的应用中,如Java的HashMap等,如果存在碰撞就会以当前数组位置为头节点,转变成一个链表。
说到这里也清楚了为啥Java中引用类型要同时重写hashCode和equals了。两个对象,实例就算一模一样,它们的hash值也不相等, 为啥不相等?? 默认的Object的hashCode方法会根据对象来计算hash值的,实例相同,但它们还是两个不同的对象啊,所以我们重写hashCode时,最简单的方法就是调用Object的hashCode方法,然后传入该引用类型的属性,让hashCode方法只根据这几个属性来计算,那么实例相同的话,它们的hash值也会相等。等hashCode比较完后,如果相等再比较实例内容,也就是equals,确保不是hash碰撞。
索引的分类
如果我们指定了一个主键,那么这个主键就是主键索引。如果我们没有指定,Mysql就会自动找一个非空的唯一索引当主键。如果没有这种字段,Mysql就会创建一个大小为6字节的自增主键。如果有多个非空的唯一索引,那么就让第一个定义为唯一索引的字段当主键,注意,是第一个定义,而不是建表时出现在前面的。
对于辅助索引来说,它们的B+树结构稍微有点特殊,它们的叶子节点存储的是主键,而不是整个数据。所以在大部分情况下,使用辅助索引查找数据,需要二次查找。但并不是所有情况都需要二次查找。比如查找的数据正好就是当前索引字段的值,那么直接返回就行。这里提一句,B+树的key就是对应索引字段的内容。
而辅助索引又有一些分类:唯一索引:不能出现重复的值,也算一种约束。普通索引:可以重复、可以为空,一般就是查询时用到。前缀索引:只适用于字符串类型数据,对字符串前几个字符创建索引。全文索引:作用是检测大文本数据中某个关键字,这也是搜索引擎的一种技术。
注意,聚集索引、非聚集索引和前面几个索引的分类并不是一个层面上的。上面的几个分类是从索引的作用来分析的。聚集、非聚集索引是从索引文件上区分的。主键索引就属于聚集索引,即索引和数据存放在一起,叶子节点存放的就是数据。数据表的.idb文件就是存放该表的索引和数据。
辅助索引属于非聚集索引,说到这也就明白了。索引和数据不存放在一起的就是非聚集索引。在MYISAM引擎中,数据表的.MYI文件包含了表的索引, 该表的 叶子节点存储索引和索引对应数据的指针,指向.MYD文件的数据。
索引的几点使用经验
经常被查询的字段;经常作为条件查询的字段;经常用于外键连接或普通的连表查询时进行相等比较字段;不为null的字段;如果是多条件查询,最好创建联合索引,因为联合索引只有一个索引文件。
经常被更新的字段、不经常被查询的字段、存在相同功能的字段
从explain开始说起吧,很显然第一个sql语句压根没用任何索引(key列内什么都没有)!第二个倒是用到索引,却是主键索引,并非你添加的fulltext索引!
接下来,分析下原因:
sql1:执行步骤:先s_a和s_a_t两表笛卡尔集,然后筛选满足on条件的,接着在从结果集中筛选满足where字句的;该过程中处理的记录条目为69*105479,并且未用到任何索引,未用到的原因可能是你先定义了一个复合索引a_concent_split(a_title_split,a_content_split),然后又定义了一个a_content_split2(a_content_split),当引擎执行查找优化时候会先用到a_content_split,可是又由于复合索引是从最左边开始(不能跳过第一个字段),而你却忽略了a_title_split字段,故未能正常使用索引。
sql2:执行步骤:先调用where字句对s_a表进行筛选形成新的s_a表,然后与s_a_t表笛卡尔积,再利用on字句筛选,最后再次利用where字句形成最终结果集;经过第一个where,该过程处理结果集会大幅少于sql1,并且该过程还用到了主键索引。你所设置的fulltext索引再次没有用到,原因是like字句中开始部分为模糊匹配%时候用不了全文索引,这与fulltext存储机制有关。
另,你说的删除速度慢,原因:设置fulltext字段设置太多,fulltext索引在更新删除大量数据时候,需要同步更改索引,你的三个fulltext压力太大!
改进方法:1、删除a_content_split索引重试 2、在删除时候打开delay_key_write变量
有关fulltext比较复杂,用的时候要谨慎设置,还有很多参数也对其有影响
另外sql语句中外连接有关on where字句也是个比较绕的地方,两者你都占了,唉,所以我写的略复杂,前天看到该问题,思忖两天这才作答
望有结果了予以回复交流!
先正面回答你的问题
数据是否重复不是建立索引的重要依据,甚至都不是依据。
只要不完全重复(所有元组的该元素都一样),那么建立索引就是有意义的。
即使当前数据完全重复,也不是不能建立索引,这种情况有点复杂,不细说了。
对于你后面的疑问,可以给你一个如何建立索引的忠告,“如何查就如何建”。
索引的建立,唯一的原因就是为了查询(广义的查询),实际上建立索引会使得数据存储所占空间变大,有时索引所占的空间会查过数据本身的空间。索引的建立也会使得数据插入时变慢,特殊情况下,慢的难以忍受,所以dba的重要工作之一,就是检查索引层级并优化。
索引建立的唯一好处,就是按照索引查询时,变快了。type,status这2个字段是否适合建立索引,就要看你是否要按照这2个字段进行检索。而检索的顺序决定了如何建立索引。
对于索引类型和索引方式,我建议就
normal
和
btree
就适用于大多数情况。若你参与的是一个大数据处理项目,对数据存储和检索有特别要求,那么需要分析多个层面,比如数据吞吐量、数据的方差、平均差等等很多参数才考虑是否用聚集索引等(mysql好像还没聚集索引),至于是否是唯一索引,我建议不使用,即使能判定数据是唯一的也不要用,全文索引也没有必要。
MySQL支持全文索引和搜索功能。在MySQL中可以在CHAR、VARCHAR或TEXT列使用FULLTETXT来创建全文索引。
FULLTEXT索引主要用MATCH()...AGAINST语法来实现搜索:
MySQL的全文搜索存在以下局限:
通常来说MySQL自带的全文搜索使用起来局限性比较大,性能和功能都不太成熟,主要适用于小项目,大项目还是建议使用elasticsearch来做全文搜索。
ElasticSearch是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据,以下简称ES。
Elasticsearch 在 Apache Lucene 的基础上开发而成,Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件。Elastic Stack 是适用于数据采集、充实、存储、分析和可视化的一组开源工具。
Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasticsearch 数据中心,再通过分词控制器去将对应的数据分词,将其权重和分词结果一并存入数据,当用户搜索数据时候,再根据权重将结果排名,打分,再将返回结果呈现给用户。
由于ES是基于RESTfull Web接口的,因此我们直接按照惯例传递JSON参数调用接口即可实现增删改查,并且不需要我们做额外的管理操作就可以直接索引文档,ES已经内置了所有的缺省操作,可以自动帮我们定义类型。
再次执行PUT,会对库中已有的id为1的数据进行覆盖,每修改一次_version字段的版本号就会加1。
默认搜索会返回前10个结果:
返回的几个关键词:
查询字符串搜索,可以像传递URL参数一样传递查询语句。
精确查询:
全文搜索:
以上两种方法都需要考虑数据更改后如何与ES进行同步。
1、InnoDB存储引擎
Mysql版本=5.5 默认的存储引擎,MySQL推荐使用的存储引擎。支持事务,行级锁定,外键约束。事务安全型存储引擎。更加注重数据的完整性和安全性。
存储格式 : 数据,索引集中存储,存储于同一个表空间文件中。
InnoDB的行锁模式及其加锁方法: InnoDB中有以下两种类型的行锁:共享锁(读锁: 允许事务对一条行数据进行读取)和 互斥锁(写锁: 允许事务对一条行数据进行删除或更新), 对于update,insert,delete语句,InnoDB会自动给设计的数据集加互斥锁,对于普通的select语句,InnoDB不会加任何锁。
InnoDB行锁的实现方式: InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁。InnoDB这种行锁实现特点意味着:如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。
(1)在不通过索引条件查询时,InnoDB会锁定表中的所有记录。
(2)Mysql的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果使用相同的索引键,是会出现冲突的。
(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,但都是通过行锁来对数据加锁。
优点:
1、支持事务处理、ACID事务特性;
2、实现了SQL标准的四种隔离级别( 原子性( Atomicity )、一致性( Consistency )、隔离性(Isolation )和持续性(Durability ));
3、支持行级锁和外键约束;
4、可以利用事务日志进行数据恢复。
5、锁级别为行锁,行锁优点是适用于高并发的频繁表修改,高并发是性能优于 MyISAM。缺点是系统消耗较大。
6、索引不仅缓存自身,也缓存数据,相比 MyISAM 需要更大的内存。
缺点:
因为它没有保存表的行数,当使用COUNT统计时会扫描全表。
使用场景:
(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且表锁定的机会比较大的情况。
2、 MyISAM存储引擎
MySQL= 5.5 MySQL默认的存储引擎。ISAM:Indexed Sequential Access Method(索引顺序存取方法)的缩写,是一种文件系统。擅长与处理,高速读与写。
功能:
(1)支持数据压缩存储,但压缩后的表变成了只读表,不可写;如果需要更新数据,则需要先解压后更新。
(2)支持表级锁定,不支持高并发;
(3)支持并发插入。写操作中的插入操作,不会阻塞读操作(其他操作);
优点:
1.高性能读取;
2.因为它保存了表的行数,当使用COUNT统计时不会扫描全表;
缺点:
1、锁级别为表锁,表锁优点是开销小,加锁快;缺点是锁粒度大,发生锁冲动概率较高,容纳并发能力低,这个引擎适合查询为主的业务。
2、此引擎不支持事务,也不支持外键。
3、INSERT和UPDATE操作需要锁定整个表;
使用场景:
(1)做很多count 的计算;(2)插入不频繁,查询非常频繁;(3)没有事务。
InnoDB和MyISAM一些细节上的差别:
1、InnoDB不支持FULLTEXT类型的索引,MySQL5.6之后已经支持(实验性)。
2、InnoDB中不保存表的 具体行数,也就是说,执行select count() from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count()语句包含 where条件时,两种表的操作是一样的。
3、对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。
4、DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。
5、LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。
6、另外,InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表。
1.索引概述
利用关键字,就是记录的部分数据(某个字段,某些字段,某个字段的一部分),建立与记录位置的对应关系,就是索引。索引的关键字一定是排序的。索引本质上是表字段的有序子集,它是提高查询速度最有效的方法。一个没有建立任何索引的表,就相当于一本没有目录的书,在每次查询时就会进行全表扫描,这样会导致查询效率极低、速度也极慢。如果建立索引,那么就好比一本添加的目录,通过目录的指引,迅速翻阅到指定的章节,提升的查询性能,节约了查询资源。
2.索引种类
从索引的定义方式和用途中来看:主键索引,唯一索引,普通索引,全文索引。
无论任何类型,都是通过建立关键字与位置的对应关系来实现的。索引是通过关键字找对应的记录的地址。
以上类型的差异:对索引关键字的要求不同。
关键字:记录的部分数据(某个字段,某些字段,某个字段的一部分)。
普通索引,index:对关键字没有要求。
唯一索引,unique index:要求关键字不能重复。同时增加唯一约束。
主键索引,primary key:要求关键字不能重复,也不能为NULL。同时增加主键约束。
全文索引,fulltext key:关键字的来源不是所有字段的数据,而是从字段中提取的特别关键词。
PS:这里主键索引和唯一索引的区别在于:主键索引不能为空值,唯一索引允许空值;主键索引在一张表内只能创建一个,唯一索引可以创建多个。主键索引肯定是唯一索引,但唯一索引不一定是主键索引。
3.索引原则
如果索引不遵循使用原则,则可能导致索引无效。
(1)列独立
如果需要某个字段上使用索引,则需要在字段参与的表达中,保证字段独立在一侧。否则索引不会用到索引, 例如这条sql就不会用到索引:select * from A where id+1=10;
(2)左原则
Like:匹配模式必须要左边确定不能以通配符开头。例如:select * from A where name like '%小明%' ,不会用到索引,而select * from A where name like '小明%' 就可以用到索引(name字段有建立索引),如果业务上需要用到'%小明%'这种方式,有两种方法:1.可以考虑全文索引,但mysql的全文索引不支持中文;2.只查询索引列或主键列,例如:select name from A where name like '%小明%' 或 select id from A where name like '%小明%' 或 select id,name from A where name like '%小明%' 这三种情况都会用到name的索引;
复合索引:一个索引关联多个字段,仅仅针对左边字段有效果,添加复合索引时,第一个字段很重要,只有包含第一个字段作为查询条件的情况才会使用复合索引(必须用到建索引时选择的第一个字段作为查询条件,其他字段的顺序无关),而且查询条件只能出现and拼接,不能用or,否则则无法使用索引.
(3)OR的使用
必须要保证 OR 两端的条件都存在可以用的索引,该查询才可以使用索引。
(4)MySQL智能选择
即使满足了上面说原则,MySQL也能弃用索引,例如:select * from A where id 1;这里弃用索引的主要原因:查询即使使用索引,会导致出现大量的随机IO,相对于从数据记录的第一条遍历到最后一条的顺序IO开销,还要大。
4.索引的使用场景
(1)索引检索:检索数据时使用索引。
(2)索引排序: 如果order by 排序需要的字段上存在索引,则可能使用到索引。
(3)索引覆盖: 索引拥有的关键字内容,覆盖了查询所需要的全部数据,此时,就不需要在数据区获取数据,仅仅在索引区即可。覆盖就是直接在索引区获取内容,而不需要在数据区获取。例如: select name from A where name like '小明%';
建立索引索引时,不能仅仅考虑where检索,同时考虑其他的使用场景。(在所有的where字段上增加索引,就是不合理的)
5.前缀索引
前缀索引是建立索引关键字一种方案。通常会使用字段的整体作为索引关键字。有时,即使使用字段前部分数据,也可以去识别某些记录。就比如一个班级里,我要找王xx,假如姓王的只有1个人,那么就可以建一个关键字为'王'的前缀索引。语法:Index `index_name` (`index_field`(N))使用index_name前N个字符建立的索引。
6.索引失效
(1) 应尽量避免在 where 子句中使用 != 或 操作符,否则将引擎放弃使用索引而进行全表扫描;
(2) 应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描;
(3) 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描;
(4)应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描;如select id from t where num/2 = 100;
(5) 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描;如:select id from t where substring(name,1,3) = ’abc’ ;
(6)应尽量避免在where子句中对字段进行类型转换,这将导致引擎放弃使用索引而进行全表扫描; 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,如select id from t where id = 1;如果id字段在表设计中是varchar类型,那么即使id列上存的是数字,在查询时也一定要用varchar去匹配,sql应改为select id from t where id = '1';
(7)应尽量避免在where子句中单独引用复合索引里非第一位置的索引;
join 的两种算法:BNL 和 NLJ
NLJ(Nested Loop Join)嵌套循环算法;以如下 SQL 为例:
select * from t1 join t2 on t1.a=t2.a
SQL 执行时内部流程是这样的:
1. 先从 t1(假设这里 t1 被选为驱动表)中取出一行数据 X;
2. 从 X 中取出关联字段 a 值,去 t2 中进行查找,满足条件的行取出;
3. 重复1、2步骤,直到表 t1 最后一行循环结束。
这就是一个嵌套循环的过程,如果在被驱动表上查找数据时可以使用索引,总的对比计算次数等于驱动表满足 where 条件的行数。假设这里 t1、t2都是1万行,则只需要 1万次计算,这里用到的是Index Nested-Loops Join(INLJ,基于索引的嵌套循环联接)。
如果 t1、t2 的 a 字段都没有索引,还按照上述的嵌套循环流程查找数据呢?每次在被驱动表上查找数据时都是一次全表扫描,要做1万次全表扫描,扫描行数等于 1万+1万*1万,这个效率很低,如果表行数更多,扫描行数动辄几百亿,所以优化器肯定不会使用这样的算法,而是选择 BNL 算法;
BNLJ(Block Nested Loop Join)块嵌套循环算法;
1. 把 t1 表(假设这里 t1 被选为驱动表)满足条件的数据全部取出放到线程的 join buffer 中;
2. 每次取 t2 表一行数据,去 joinbuffer 中进行查找,满足条件的行取出,直到表 t2 最后一行循环结束。
这个算法下,执行计划的 Extra 中会出现 Using join buffer(Block Nested Loop),t1、t2 都做了一次全表扫描,总的扫描行数等于 1万+1万。但是由于 joinbuffer 维护的是一个无序数组,每次在 joinbuffer 中查找都要遍历所有行,总的内存计算次数等于1万*1万。另外如果 joinbuffer 不够大放不下驱动表的数据,则要分多次执行上面的流程,会导致被驱动表也做多次全表扫描。
BNLJ相对于NLJ的优点在于,驱动层可以先将部分数据加载进buffer,这种方法的直接影响就是将大大减少内层循环的次数,提高join的效率。
例如:
如果内层循环有100条记录,外层循环也有100条记录,这样的话,每次外层循环先将10条记录放到buffer中,内层循环的100条记录每条与这个buffer中的10条记录进行匹配,只需要匹配内层循环总记录数次即可结束一次循环(在这里,即只需要匹配100次即可结束),然后将匹配成功的记录连接后放入结果集中,接着,外层循环继续向buffer中放入10条记录,同理进行匹配,并将成功的记录连接后放入结果集。后续循环以此类推,直到循环结束,将结果集发给client为止。
可以发现,若用NLJ,则需要100 * 100次才可结束,BNLJ则需要100 / block_size * 100 = 10 * 100次就可结束,大大减少了循环次数。
JOIN 按照功能大致分为如下三类:
JOIN、STRAIGHT_JOIN、INNER JOIN(内连接,或等值连接):取得两个表中存在连接匹配关系的记录。
LEFT JOIN(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录。
RIGHT JOIN(右连接):与 LEFT JOIN 相反,取得右表(table2)完全记录,即是左表(table1)并无匹配对应记录。
注意:mysql不支持Full join,不过可以通过UNION 关键字来合并 LEFT JOIN 与 RIGHT JOIN来模拟FULL join。
mysql 多表连接查询方式,因为mysql只支持NLJ算法,所以如果是小表驱动大表则效率更高;反之则效率下降;因此mysql对内连接或等值连接的方式做了一个优化,会去判断join表的数据行大小,然后取数据行小的表为驱动表。
INNER JOIN、JOIN、WHERE等值连接和STRAIGHT_JOIN都能表示内连接,那平时如何选择呢?一般情况下用INNER JOIN、JOIN或者WHERE等值连接,因为MySQL 会按照"小表驱动大表的策略"进行优化。当出现需要排序时,才考虑用STRAIGHT_JOIN指定某张表为驱动表。
两表JOIN优化
a.当无order by条件时,根据实际情况,使用left/right/inner join即可,根据explain优化 ;
b.当有order by条件时,如select * from a inner join b where 1=1 and other condition order by a.col;使用explain解释语句;
1)如果第一行的驱动表为a,则效率会非常高,无需优化;
2)否则,因为只能对驱动表字段直接排序的缘故,会出现using temporary,所以此时需要使用STRAIGHT_JOIN明确a为驱动表,来达到使用a.col上index的优化目的;或者使用left join且Where条件中不含b的过滤条件,此时的结果集为a的全集,而STRAIGHT_JOIN为inner join且使用a作为驱动表。注:使用STRAIGHT_JOIN虽然不会using temporary,但也不是一定就能提高效率,如果a表数据远远超过b表,那么有可能使用STRAIGHT_JOIN时比原来的sql效率更低,所以怎么使用STRAIGHT_JOIN,还是要视情况而定。
在使用left join(或right join)时,应该清楚的知道以下几点:
(1). on与 where的执行顺序
ON 条件(“A LEFT JOIN B ON 条件表达式”中的ON)用来决定如何从 B 表中检索数据行。如果 B 表中没有任何一行数据匹配 ON 的条件,将会额外生成一行所有列为 NULL 的数据,在匹配阶段 WHERE 子句的条件都不会被使用。仅在匹配阶段完成以后,WHERE 子句条件才会被使用。它将从匹配阶段产生的数据中检索过滤。
所以我们要注意:在使用Left (right) join的时候,一定要在先给出尽可能多的匹配满足条件,减少Where的执行。
(2).注意ON 子句和 WHERE 子句的不同
即使右表的数据不满足ON后面的条件,也会在结果集拼接一条为NULL的数据行,但WHERE后面的条件不一样,右表不满足WHERE的条件,左表关联的数据也会被过滤掉。
(3).尽量避免子查询,而用join
往往性能这玩意儿,更多时候体现在数据量比较大的时候,此时,我们应该避免复杂的子查询。
(1)in 和 not in 要慎用,如:select id from t where num in(1,2,3)对于连续的数值,能用 between 就不要用 in:select id from t where num between 1 and 3很多时候用 exists 代替 in 是一个好的选择:select num from a where num in(select num from b)用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)
(2)Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
(3)join语句,MySQL里面的join是用小表去驱动大表,而由于MySQL join实现的原理就是做循环,比如left join就是对左边的数据进行循环去驱动右边的表,左边有m条记录匹配,右边有n条记录那么就是做m次循环,每次扫描n行数据,总扫面行数是m*n行数据。左边返回的结果集的大小就决定了循环的次数,故单纯的用小表去驱动大表不一定的正确的,小表的结果集可能也大于大表的结果集,所以写join的时候尽可能的先估计两张表的可能结果集,用小结果集去驱动大结果集.值得注意的是在使用left/right join的时候,从表的条件应写在on之后,主表应写在where之后.否则MySQL会当作普通的连表查询;
(4)select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的;
(5)select * from t 这种语句要尽量避免,使用具体的字段代替*,更有实际意义,需要什么字段就返回什么字段;
(6)数据量大的情况下,limit要慎用,因为使用limit m,n方式分页时,mysql每次都是查询前m+n条,然后舍弃前m条,所以m越大,偏移量越大,性能就越差。比如:select * from A limit 1000000,20这钟,查询效率就会非常低,当分页的页数大于一定的数量之后,就可以换种方式来分页:select * from A a join (select id from A limit 1000000,20) b on a.id=b.id;
InnoDB的全文索引使用反向索引的设计。反向索引存储了一个单词(word)列表,对于每个单词,都有一个文档的列表,来标识这个单词出现的地方。为了支持临近搜索(proximity search),每个单词的位置信息也以字节偏移的方式存储。
当创建了InnoDB全文索引,一系列的索引表会一同被创建,见下面的例子:
最前面的六个表包含了反向索引,它们被称作附属索引表(auxiliary index table)。当输入的表被索引(tokenized)后,每个独立的单词(亦称作“tokens”)会被携带其DOC_ID和位置信息插入到索引表中。根据单词第一个字符的字符集排序权重,在六个索引表中对单词进行完全排序和分区。
反向索引分区到六个附属索引表以支持并行的索引创建。默认有2个线程复制索引(Tokenize)、排序、插入单词和关联数据到索引表中。工作的线程的数量由 innodb_ft_sort_pll_degree 配置项控制的。对于大表的全文索引,可以考虑增加线程数量。
如果主表创建在 xx表空间,索引表存储在它们自己的表空间中。反之,索引表存储于其索引的表空间中。
前面例子展示的另外一种索引表被称作通用索引表,它们被用于全文索引的“删除处理(deletion handing)”和存储内部状态。不同于为每个全文索引都各自创建的反向索引表,这组表对特定表的所有全文索引都是共用的。
即使全文索引删掉了,通用索引(Common Index)也会被保留,当全文索引删除后,为这个索引而创建的FTS_DOC_ID列依然保留,因为移除FTS_DOC_ID列会导致重构之前被索引的表。管理FTS_DOC_ID列需要用到通用索引表。
为了防止大量并发读写附属表,InnoDB使用全文索引缓存去临时缓存最近的插入行。在存满并刷入磁盘之前,缓存的内容一直存储在内存之中,可以通过查询 INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE 表去查看最近缓存的插入行。
缓存和批处理刷新行为避免了对辅助索引表的频繁更新,频繁更新可能会在繁忙的插入和更新期间导致并发访问问题。批处理还避免了对同一个word的多次插入,最大化的减少了重复的条目。相同的word会先merge再刷入到磁盘中,而不是为每个word单独插入,这样提高了插入效率并且使得索引附属表尽可能的小。
全文索引缓存只缓存最近插入的行,查询时,已经刷入磁盘(附属索引表)的数据不会再回到索引缓存中。附属索引表中的内容是直接查询的,最终返回的结果返回前需要将附属索引表的结果和索引缓存中的结果合并。
InnoDB使用被称作DOC_ID的唯一文件描述符,将全文索引中的单词与该单词在文档中的记录映射起来。映射关系需要索引表中的 FTS_DOC_ID 列。在创建全文索引时,如果没有定义 FTS_DOC_ID 列,InnoDB会自动的加入一个隐藏的 FTS_DOC_ID 列。下面是一个例子,
CREATE FULLTEXT INDEX ft_index ON xxxxxxxx(CONTEXT)
[2021-11-12 18:14:04] [HY000][124] InnoDB rebuilding table to add column FTS_DOC_ID
重点看一下这一行: [HY000][124] InnoDB rebuilding table to add column FTS_DOC_ID ,InnoDB重新构建了这个表,并且添加了一个列 FTS_DOC_ID 。
在CREATE TABLE的过程中添加 FTS_DOC_ID 的时间成本要低于在已经有数据的表上建立全文索引。如果在表加载数据之前定义 FTS_DOC_ID 列,这个表和它的索引都不需要为了新增列而重新构建。如果你不需要考虑 CREATE FULLTEXT INDEX 的性能,可以让InnoDB为你创建 FTS_DOC_ID 列。InnoDB会新增一个隐藏的 FTS_DOC_ID 列,并且在 FTS_DOC_ID 上建立唯一索引(FTS_DOC_ID_INDEX)。如果你想自行创建 FTS_DOC_ID 列,这个列必须定义为 BIGINT UNSIGNED NOT NULL 且命名为FTS_DOC_ID(全大写),如下例子:
如果你自行定义 FTS_DOC_ID 列的话,你需要负责管理这个列,避免空值(empty)或者重复值。 FTS_DOC_ID 的值是不能被重复利用的,所以也就是说 FTS_DOC_ID 的值是需要一直增加的。
或者,你可以在 FTS_DOC_ID 列上创建所必须的唯一索引FTS_DOC_ID_INDEX(全大写)。
mysql CREATE UNIQUE INDEX FTS_DOC_ID_INDEX on opening_lines(FTS_DOC_ID);
如果你没有创建FTS_DOC_ID_INDEX,InnoDB会自动创建。
在MySQL 5.7.13前,允许最大FTS_DOC_ID与最新的FTS_DOC_ID之间的间隔为10000,在MySQL 5.7.13及之后的版本中,这个允许的间隔为65535。
为了避免重新构建表,FTS_DOC_ID列在删除了全文索引之后依然被保留。
删除被索引文件的一个记录,可能会在附属索引表中产生非常多的小的删除项,在并发访问时,会产生热点问题。为了避免这个问题,每当被索引表中的记录被删除时,会将被删文档的DOC_ID记录在一个特别的 FTS_*_DELETED 表中,同时全文索引中已经索引了的记录依然被保存。在返回查询结果前,使用 FTS_*_DELETED 中的信息去过滤掉已经删除掉了的DOC_ID。这种设计的优势在于删除速度快且消耗低。不好的地方在于索引的大小不能随着记录的删除而立即减少。为了删除已删除记录在全文索引中的项,需要对被索引的表执行OPTIMIZE TABLE,配合[ innodb_optimize_fulltext_only=ON ],去重构全文索引。
细节略,有例子:
全文搜索只能看到已经提交了的数据。
你可以通过查询下面的INFORMATION_SCHEMA表,来监控或测试InnoDB的一些特殊文本处理。
默认的分词器不支持中文,不能检索到中文中的英文单词。
InnoDB默认的Stopwords:
select * from information_schema.INNODB_FT_DEFAULT_STOPWORD;
SQL中的关键词(保留关键字):
Shell中的关键词:for,while,echo
其他:###, ***, --,
被索引表数据量、索引表数据量
模糊匹配与严格匹配的性能差距
需要将 innodb_optimize_fulltext_only 配置为ON,这里是否需要DBA在MySQL镜像中修改?
innodb_optimize_fulltext_only 设置为ON后,对系统有何影响需要评估。
innodb_optimize_fulltext_only
执行的时间、频率。
MySQL内建的全文检索解析器使用单词之间的空白作为分隔符以标识单词的头尾,但是这里有个限制,对于表意文字,它是没有单词分隔符的。为了解决这个限制,MySQL提供了支持中文、日语、韩语的 ngram 解析器。ngram解析器支持InnoDB和MyISAM。
Ngram是内建在服务中的插件,像其他自建在服务中的插件一样,服务启动时会自动加载它。全文检索的语法参考上面( Section 12.10, “Full-Text Search Functions” ),这里只讨论一些不同的地方。除了单词的最小、最大长度配置项([ innodb_ft_min_token_size ]innodb_ft_max_token_size,ft_min_word_len,ft_max_word_len,全文检索依赖一些配置项都是可以使用的。
Ngram默认索引的单词(token)的大小为2( 2bigram )。例如,索引的大小为2,Ngram解析器解析字符串“abc def”为四个单词元素(tokens):“ab”, “bc”, “de” and “ef”。
ngram token size is configurable using the ngram_token_size configuration option, which has a minimum value of 1 and maximum value of 10.
作为只读变量, ngram_token_size 只能在启动配置或者配置文件中指定
与默认的解析器相差不大,多了一句: xxx WITH PARSER ngram
Ngram在解析时去除空格,如
MySQL内建的默认全文检索解析器将单词与Stopword列表中的做对比,如果单词与Stopword列表中的元素相同的话,这个单词则不会被索引。对于Ngram解析器,Stopword的处理方式不同。Ngram解析器不排除与stopword列表中的条目相等的token,而是排除包含stopwords的token。例如,假设 ngram_token_size=2 ,包含“a,b”的文档将被解析为 “a,” h和“,b”。如果将逗号(“,”)定义为停止字,则 “a,”和“,b”都将不会加入索引中,因为它们包含逗号。
例子:
默认Ngram解析器使用默认的Stopword列表,这里面含有英文的Stopword。如果需要中文的Stopword,需要你自己创建。
Stopword的长度超过 ngram_token_size则会被忽略。
有两个文档,一个包含“ab”,另一个包含“abc”。对于搜索文本“abc”将转换成“ab”,“bc”。
略。
For example, The search phrase “abc” is converted to “ab bc”, which returns documents containing “abc” and “ab bc”.
The search phrase “abc def” is converted to “ab bc de ef”, which returns documents containing “abc def” and “ab bc de ef”. A document that contains “abcdef” is not returned.
使用Ngram解析器好处是支持了中文的检索
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流