引言
索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构
。
常见的索引类型包括B-Tree索引、哈希索引、空间数据索引(R-Tree)、全文索引。
索引的类型
在MySQL中,索引是在 存储引擎层
而不是服务层实现的。所以,并没有统一的索引标准:不同存储引擎的索引工作方式并不一样,
也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。
B-Tree索引
B-Tree索引是最常用的一种索引结构,他使用B-Tree数据结构来存储数据,大多数MySQL引擎都支持这种索引,B-Tree通常意味着所有的值都是按顺序存储的,如图展示了B-Tree索引的抽象表示。
B-Tree索引能够加快访问数据的速度,因为存储引擎不在需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找,通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点页中值的上限和下限。最终存储引擎要么是找到对应的值,要么该记录不存在。
B-Tree对索引列是按照顺序组织存储的,所以很适合查找范围数据,例如,在一个基于文本域的索引树上,按字母顺序传递连续的值进行查找是非常合适的,所以像 “找出所有以I到K开头的名字” 这样的查找效率会非常高。
假设有如下数据表
对于表中的每一行数据,索引中包含了last_name、first_name和dob列的值,下图展示了该索引是如何组织数据的存储
注意,索引对多个值进行排序的依据是create table语句中定义索引时列的顺序
。
可以使用B-Tree索引的查询类型,B-Tree索引适用于 全键值、键值范围或键前缀查找
。
全值匹配
全值匹配指的是和索引中的所有列进行匹配。
匹配最左前缀
前面提到的索引可用于查找所有姓为Allen的人,即只有索引的第一列。
匹配列前缀
也可以只匹配某一列的值的开头部分,例如前面提到的索引可用于查找所有以J开头的姓的人,这里只使用了索引的第一列。
匹配范围值
前面提到的索引可用于查找姓在Allen和Barry之间的人,这里也只使用了索引的第一列。
精确匹配某一列并范围匹配另外一列
前面提到的索引也可以用于查找所有姓为Allen,并且名字是以字母K开头的人。即第一列last_name全匹配,第二列first_name范围匹配。
只访问索引的查询
B-Tree通常可以支持 “只访问索引的查询”,即查询只需要访问索引,无需访问数据行。后面将单独讨论这种“覆盖索引”的优化。
索引下推
索引下推是MySQL5.6版本中提供的一项索引优化功能,他允许存储引擎在索引遍历过程中,执行部分where子句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
示例如下:
- 在没有索引下推之前,即使zipcode字段利用索引可以帮助我们快速定位到zipcode=‘431200’的用户,但我们仍然需要对每一个找到的用户进行回表操作,获取完整的用户数据,再去判断MONTH(birthdate) = 3.
- 有了索引下推之后,存储引擎会在使用zipcode字段索引查找zipcode=‘431200’的用户时,同时判断
MONTH(birthdate) = 3.这样,只有同时满足条件的记录才会被返回,减少了回表次数。
索引下推的原理
MySQL可以简单分为Server层和存储引擎层,Server层处理查询解析、分析、优化、缓存以及客户端的交互等操作,而存储引擎层负责数据的存储和读取,MySQL支持InnoDB、MyISAM、Memory等多种存储引擎。
索引下推的下推就是将部分上层(Server层)负责的事情,交给了下层(存储引擎)去处理。
没有索引下推之前,执行流程
- 存储引擎层先根据zipcode索引字段找到所有zipcode='431200’的用户主键ID,然后二次回表查询,获取完整的用户数据。
- 存储引擎层把所有zipcode='431200’的用户数据全部交给server层,Server层根据MONTH(birthdate) = 3这一条件再进一步做筛选。
有索引下推之后,执行流程
- 存储引擎层先根据 zipcode 索引字段找到所有 zipcode = ‘431200’ 的用户,然后直接判断 MONTH(birthdate) = 3,筛选出符合条件的主键 ID。
- 二次回表查询,根据符合条件的主键ID去获取完整的用户数据。
- 存储引擎把符合条件的用户数据全部交给Server层。
可以看出,除了减少回表次数外,索引下推还可以减少存储引擎层和Server层的数据传输量。
关于B-Tree索引的限制
-
如果不是按照索引的最左列开始查找,则无法使用索引
。例如,上面例子中的索引无法用于查找名字为Bill的人,也无法查找某个特定生日的人,因为这两列都不是最左数据列,类似的,也无法查找姓氏以某个字母结尾的人。 -
不能跳过索引中的列
,也就是说,前面所述的索引无法用于查找姓为Smith并且在某个特定日期出生的人。如果不指定名first_name,则MySQL只能使用索引的第一列。 -
如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找
。例如有查询where last_name = ‘Smith’ And first_name like ‘J%’ and dob = ‘1976-12-23’,这个查询只能使用索引的前两列,因为这里like是一个范围条件。如果范围查询列值得数量有限,那么可以通过使用多个等于条件来代替范围条件。
哈希索引
哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。
假设有如下表:
表中包含如下数据
假设索引使用假想的哈希函数f(), 他返回下面的值
则哈希函数的索引结构如下
注意每个槽的编号是顺序的,但是数据行不是,现在,来看如下查询:
MySQL先计算‘Peter’的哈希值,并使用该值寻找对应的记录指针。因为f(‘Peter’) = 8784,所以MySQL在索引中查找8784,可以找到指向第3行的指针,最后一步是比较第三行的值是否为‘Peter’,以确保就是要查找的行。
因为索引自身只需要存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。然而,哈希索引也有他的限制。
哈希索引的限制
- 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行速度是很快的,所以大部分情况下这一点对性能影响并不明显。
- 哈希索引数据并不是按照索引值顺序存储的,所以
无法用于排序
。 - 哈希索引也
不支持部分索引列匹配查找
,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。例如,在数据列(A,B)上建立哈希索引,如果查询只有数据列A,则无法使用索引。 哈希索引只支持等值比较查找,不支持范围查询
。- 访问哈希索引的数据非常快,除非有很多哈希冲突,
当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行
。 - 如果哈希冲突很多的话,一些索引维护操作的代价也会很高。例如,在某个选择性很低(哈希冲突)的列上建立哈希索引,那么当从表中删除一行时,存储引擎需要遍历对应哈希值的链表中的每一行,找到并删除对应行的引用,冲突越多,代价越大。
创建自定义哈希索引(索引优化思路)
InnoDB引擎有一个特殊的功能叫做“自适应哈希索引”,当InnoDB注意到某些索引值被使用的非常频繁时,他会在内存中基于B-Tree索引之上在创建一个哈希索引,这样就让B-Tree索引也具有哈希索引的一些优点,比如快速的哈希查找,这是一个完全自动的、内部的行为,用户无法控制或者配置,不过如果有必要,可以关闭该功能。
如果存储引擎不支持哈希索引,可以模拟创建一个自定义哈希索引,具体思路如下
在B-Tree基础上创建一个伪哈希索引,这和真正的哈希索引不是一回事,因为还是使用B-Tree进行查找,但是他使用哈希值而不是键本身进行索引查找。
例如,需要存储大量的URL,并需要根据URL进行搜索查找,如果使用B-Tree来存储URL,存储的内容就会很大。因为URL本身都很长,正常情况下使用如下查询:
若删除原来URL列上的索引,而新增一个被索引的url_crc列,使用crc32做哈希,就可以使用下面的方式查询:
这样做的性能会非常高。因为MySQL优化器会使用这个选择性很高而体积很小的基于url_crc列的索引来完成查找。即使有多个记录有相同的索引值,查找仍然很快,只需要根据哈希值做快速的整数比较就能找到索引条目,然后一一比较返回对应的行。另外一种方式就是对完整的url字符串做索引,效率会非常低。
这样实现的缺陷是需要维护哈希值。
如果数据表非常大,CRC32()会出现大量的哈希冲突,则可以考虑自己实现一个简单的64位哈希函数,这个自定义函数要返回整数(本质上还是用B-Tree建立索引),而不是字符串。
空间数据索引(R-Tree)
MyISAM表支持空间索引,可以用作地理数据存储。和B-Tree索引不同,这类索引无法前缀查询,空间索引会从所有维度来索引数据。查询时,可以有效的使用任意维度来组合查询,必须使用MySQL的GIS相关函数如MBRCONTAINS()来维护数据,MySQL的GIS支持并不完善,所以大部分人都不会使用这个特性。
全文索引
全文索引是一种特殊类型的索引,他查找的是文本中的关键词,而不是直接比较索引中的值。全文搜索和其他几类索引匹配方式完全不一样,他有许多需要注意的细节,如停用词、词干和复数、布尔搜索等。全文索引更类似于搜索引擎做的事情。
索引的优点
- 索引大大减少了服务器需要扫描的数据量。
- 索引可以帮助服务器避免排序和临时表。
- 索引可以将随机IO变为顺序IO。
索引是最好的解决方案吗?
索引并不是总是最好的工具。总的来说,只有当索引帮助存储引擎快速查找到记录带来的好处大于其带来的额外工作时,索引才是有效的。对于非常小的表,大部分情况下,简单的全表扫描更加高效。对于中到大型的表,索引就非常有效。对于特大型的表,建立和使用索引的代价也将随之增长,这种情况下,则需要一种技术可以直接区分出查询需要的一组数据。而不是一条记录一条记录的匹配。例如,可以使用分区技术。
高性能的索引策略
独立的列
如果查询中的列不是独立的,则MySQL就不会使用索引,“独立的列” 是索引列不能是表达式的一部分,也不能是函数的参数。
例如,下面这个查询无法使用actor_id列的索引
MySQL无法自动解析这个方程式,这完全是用户行为。我们应该养成简化where条件的习惯,始终将索引列单独放在比较符号的一侧。
下面是另一个常见的错误
前缀索引和索引选择性
有时候需要索引很长的字符列如前面所述的url,这会让索引变得大且慢,一个策略是前面提到过的模拟哈希索引,除此之外,还可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。索引的选择性指,不重复的索引值(也称为基数)和数据表的记录总数的比值。索引的选择性越高则查询效率越高
,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行,唯一索引的选择性是1,这时最好的索引选择性,性能也是最好的。
一般情况下某个列前缀的选择性也是足够高的,足以满足查询性能。对于Blob、Text或者很长的Varchar类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。
为了决定前缀的合适长度,需要找到最常见的值的列表,然后和最常见的前缀列表进行比较。实例如下。
注意到,上面每个值都出现了45~65次。现在查找最频繁出现的城市前缀,先从3个前缀字母开始
每个前缀都比原来的城市出现的次数更多,因为唯一前缀比唯一城市要少得多。然后增加前缀长度,直到这个前缀的选择性接近于完整列的选择性,经过实现发现,前缀长度为7时比较合适
计算合适的前缀长度的另一个办法就是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性,如下所示
计算完整列的选择性
计算前缀选择性
在上面实例中,已经找到了合适的前缀长度,下面展示如何创建前缀索引
前缀索引是一种能使的索引更小、更快的有效方法
。但另一方面的缺点是,MySQL无法使用前缀索引做Order By和Group By也无法使用前缀索引做覆盖扫描。
多列索引
在多个列上建立独立的单列索引大部分情况下并不能提高MySQL的查询性能。MySQL5.0和更新版本引入了一种叫 “索引合并” 的策略,一定程度上可以使用表上的多个单列索引来定位指定的行。例如
在老的MySQL版本中,MySQL会对这个查询使用全表扫描,除非改写成如下的两个查询UNION的方式。
但在MySQL5.0和更新的版本中,查询能够同时使用这两个单列索引进行扫描, 并将结果进行合并。下面的查询就是使用了两个索引扫描的联合,通过EXPLAIN中的Extra列可以看到
MySQL会使用这类技术优化复杂查询,所以在某些语句的Extra列中还可以看到嵌套操作。
索引合并策略有时候是一种优化的结果,但实际上更多时候说明了表上的索引建的很糟糕:
- 当出现服务器对多个索引做相交操作时(通常有多个And条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
- 当服务器需要对多个索引做联合操作时(通常有多个OR条件),通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据的时候。
- 更重要的是,优化器不会把这些计算到“查询成本”中,优化器只关心随机页面读取。这会使得查询的成本被“低估”,导致该执行计划还不如直接走全表扫描。
如果在EXPLAIN中看到有索引合并,应该好好检查一下查询和表的结构,看看是不是已经是最优的。
选择合适的索引列顺序
对于如何选择索引的列顺序有一个经验法则:将选择性最高的列放到索引最前列。
如下,
可以看出,customer_id的选择性更高,所以按照经验法则,将其作为索引列的第一列。
聚簇索引
聚簇索引并不是一种单独的索引类型 ,而是一种数据存储方式。InnoDB的聚簇索引实际上是在同一个结构中保存了B-Tree索引和数据行。
当表有聚簇索引时,他的数据行实际上是存放在索引的叶子页中。术语“聚簇” 表示数据行和相邻的键值紧凑的存储在一起。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
图中展示了聚簇索引中的记录是如何存放的,注意到,叶子页包含了行的全部数据,但是节点页只包含了索引列。在这个案例中,索引列包含的是整数值。
InnoDB通过主键聚集数据,也就是图中“被索引的列”是主键列
。
如果没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。
聚簇索引的重要优点
- 可以把相关数据保存在一起,例如,实现电子邮箱时,可以根据用户ID来聚集数据,这样只需要从磁盘读取少数的数据页就能获取某个用户的全部邮件。如果没有使用聚簇索引,则每封邮件都可能导致一次磁盘IO。
- 数据访问更快,聚簇索引将索引和数据保存在同一个B-Tree中,因此从聚簇索引中获取数据通常比在非聚簇索引中查找要快。
- 使用覆盖索引扫描的查询可以直接使用叶节点中的主键值。
同时,聚簇索引也有一些缺点
- 聚簇数据最大限度的提高了IO密集型应用的性能,但如果数据全部都放在内存中,则访问的顺序就没那么重要了,聚簇索引也就没什么优势了。
- 插入速度严重依赖于插入顺序。按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式,但如果不是按照主键顺序加载数据,那么在加载完成后最好使用OPTIMIZE TABLE命令重新组织一下表。
- 更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
- 基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临“页分裂”的问题。当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,这就是一次页分裂操作。页分裂会导致表占用更多的磁盘空间。
- 聚簇索引可能导致全表扫描更慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候。
- 二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子结点包含了引用行的主键列。
二级索引访问需要两次索引查找,而不是一次
。
回表操作:
二级索引叶子节点中保存的不是指向行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子结点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了大量重复的工作:两次B-Tree查找而不是一次,对于InnoDB,自适应哈希索引能够减少这样的重复工作。
InnoDB和MyISAM的数据分布对比
假设该表的主键取值为1~10 000,按照随机顺序插入并使用OPTIMIZE TABLE命令做了优化,换句话说,数据在磁盘上的存储方式已经是最优的,但行的顺序是随机的。列col2的值是从1~100之间的随机赋值,所以有很多重复的值。
MyISAM的数据分布
MyISAM的数据分布非常简单,按照数据插入的顺序存储在磁盘上。
在行的旁边显示了行号,从0开始递增,因为行是定长的,所以MyISAM可以从表的开头跳过所需的字节找到需要的行。
MyISAM主键索引分布
MyISAM col2列索引分布
事实上,MyISAM中主键索引和其他索引在结构上没有什么不同,主键索引就是一个名为Primary的唯一非空索引。
InnoDB的数据分布
在InnoDB中,聚簇索引“就是”表,所以不像MyISAM中那样需要独立的行存储。
聚簇索引的每一个叶子节点都包含了主键值、事务ID、用于事务和MVCC的回滚指针以及所有的剩余列。
还有一点和MyISAM不同的是,InnoDB的二级索引和聚簇索引很不相同,InnoDB二级索引的叶子节点中存储的不是“行指针”,而是主键值。并以此作为指向行的指针,这样的策略减少了当出现行移动或者数据分裂时更新二级索引的维护工作。使用主键值当做指针会让二级索引占用更多的空间,换来的好处是,InnoDB在移动行时无需更新二级索引中的这个指针。
InnoDb和MyISAM存放数据抽象示意图
在InnoDB表中按主键顺序插入行
如果正在使用InnoDB表并且没有什么数据需要聚集,那么定义一个代理键作为主键,这种主键的数据应该和应用无关,最简单的方法是使用AUTO_INCREMENT自增列,这样可以保证数据行是按顺序写入的,对于根据主键做关联操作的性能也更好。
最好避免随机的聚簇索引,特别是对于IO密集型的应用。例如,从性能的角度考虑,使用UUID作为聚簇索引则会很糟糕,他使得聚簇索引的插入变得完全随机,这是最坏的情况,使得数据没有任何聚集特性。
基准测试,首先,我们在一个有足够内存容纳索引的服务器上向这两个表插入100w条记录,然后向这两个表继续插入300w条记录,使得索引的大小超过服务器的内存容量。效果对比
注意到向UUID主键插入行不仅花费时间更长,而且索引占用的空间也更大,这一方面是由于主键字段更长,另一方面是由于页分裂和碎片导致的。
如图所示,因为主键的值是顺序的,所以InnoDB把每一条记录都存储在上一条记录的后面,当达到页的最大填充因子时(InnoDB默认的最大填充因子是页大小的15/16,留出部分空间用于以后修改),下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近似于被顺序的记录填满,这也是希望的结果(二级索引页可能不一样)。
对比使用UUID作为聚簇索引。
因为新行的主键值不一定比之前插入的大,所以InnoDB无法简单的总是把新行插入到索引的最后,而是需要为新的行寻找合适的位置——通常是已有数据的中间位置——并且分配空间。这会增加很多额外工作,并导致数据分布不够优化。下面是总结的一些缺点
- 写入的目标页可能已经刷到磁盘上并从缓存中移除,或者是还没有被加载到缓存中,InnoDB在插入前不得不先找到从磁盘读取目标页到内存中,这将导致大量的随机IO。
- 因为写入是乱序的,InnoDb不得不频繁的做页分裂操作,以便为新的行分配空间。页分裂会导致移动大量数据,一次插入最少需要修改三个页而不是一个页。
- 由于频繁的页分裂,页会变得稀疏并被不规则的填充,所以最终数据会有碎片。
总结:使用InnoDB时应该尽可能的按照主键顺序插入数据,并且尽可能的使用单调增加的聚簇键的值来插入新行。
覆盖索引
通常大家会根据查询的where条件来创建合适的索引,不过这只是索引优化的一个方面。设计优秀的索引应该考虑到整个查询,而不单单是where条件部分。索引确实是一种查找数据的高效方式,但是MySQL也可以使用索引来直接获取列的数据。这样就不需要在读取数据行。如果索引的叶子节点中已经包含要查询的数据,那么还有什么必要在回表查询,如果一个索引包含所有需要查询的字段的值,我们就称之为“覆盖索引”。
覆盖索引的好处:
- 索引条目通常远小于数据行大小,所以如果只需要读取索引,那么MySQL就会极大减少数据访问量。这对缓存的负载非常重要,因为这种情况下响应时间大部分花费在数据拷贝上。覆盖索引对于IO密集型应用也有帮助,因为索引比数据更少,更容易放入内存。
- 使得简单的范围查询能使用完全顺序的索引访问。
- 由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。
当发起一个索引覆盖的查询时,在EXPLAIN的Extra列可以看到“Using index”的信息,例如,
使用索引扫描来做排序
MySQL有两种方式可以生成有序的结果:通过排序操作;或者按照索引顺序扫描。如果explain出来的type列的值为“index”,则说明MySQL使用了索引扫描来做排序。
扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就都回表查询一次对应的行。这基本上都是随机IO。因此按照索引顺序读取数据的速度通常要比顺序的全表扫描慢,尤其是在IO密集型的工作负载时。
MySQL可以使用同一个索引既满足排序,又用于查找行。只有当索引列的顺序和ORDER BY 子句的顺序完全一致,并且所有列的排序方向(倒序或正序)都一样时,MySQL才能够使用索引来对结果做排序。
ORDER BY 子句和查找型查询的限制是一样的:需要满足索引的最左前缀的要求
;否则MySQL都需要执行排序操作,而无法利用索引排序。
有一种情况下ORDER BY 子句可以不满足索引的最左前缀匹配的要求,就是前导列为常量的时候。
式例如下:
MySQL可以使用rental_date索引为下面的查询做排序(前导列为常量,order by子句中满足最左匹配原则)。
下面是一些不能使用索引做排序的查询
- 使用两种不同的排序方向
- 引用一个不在索引中的列
- where和Order by的列无法组合成索引的最左前缀
- 索引列的第一列上是范围条件,所以MySQL无法使用索引的其余列
冗余和重复索引
冗余索引和重复索引有一些不同,如果创建了索引(A,B),再创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。因为索引(A,B)也可以当作索引(A)来使用。但是如果再创建索引(B,A),则不是冗余索引,索引(B)也不是,因为B不是索引(A,B)的最左前缀列。另外,其他不同类型的索引(例如哈希索引)也不会是B-Tree索引的冗余索引,而无论覆盖的索引列是什么。
索引和锁
InnoDB只有在访问行的时候才会对其加锁,而索引能够减少InnoDB访问的行数,从而减少锁的数量。但这只有当InnoDB在存储引擎层能够过滤掉所有不需要的行时才有效。如果索引无法过滤掉无效的行,那么在InnoDB检索到数据并返回给服务器层以后,MySQL服务器才能应用where子句,此时已经无法避免锁定行。
示例如下:
查询返回结果
这条查询仅仅返回2~4之间的行,但是实际上获取了1~4之间的行的排他锁。InnoDB会锁住第一行,这是因为MySQL为该查询选择的执行计划是索引范围扫描。
换句话说,底层存储引擎的操作是“从索引的开头开始获取满足条件actor_id<5”的记录,在Extra列出现了“Using where”,这表示MySQL服务器将存储引擎返回行以后在应用where条件过滤。
在执行如下查询,该查询将会被挂起,直到第一个事务释放第一行的锁
如上所述,即使使用了索引,InnoDB也可能锁住一些不需要的数据,如果不使用索引查找和锁定行的问题可能会更糟糕,MySQL会做全表扫描并锁住所有的行。