MySQL整理【01】
SQL执行顺序
MySQL服务端逻辑架构
处理连接:
- 维护与客户端通信的线程
- 对客户端的身份进⾏认证
- MySQL数据库默认100个连接,最大16384。
查询缓存:
- 如果两个查询请求在任何字符上的不同(例如:空格、注释、⼤⼩写),都会导致缓存不会命中
- 如果查询请求中包含某些系统函数、⽤户⾃定义变量、函数、⼀些系统表(如 mysql 、information_schema、performance_schema 数据库中的表),那这个请求就不会被缓存
- 缓存失效:MySQL的缓存系统会监测涉及到的每张表,只要该表的结构或者数据被修改,如对该表使⽤了INSERT、 UPDATE…,就会缓存失效。
- 虽然查询缓存有时可以提升系统性能,但也不得不因维护这块缓存⽽造成⼀些开销,⽐如每次都要去查询缓存中检索,查询请求处理完需要更新查询缓存,维护该查询缓存对应的内存区域。从MySQL5.7.20开始,不推荐使⽤查询缓存,并在MySQL 8.0中删除。
语法解析:词法分析、语法分析、生成语法树
查询优化:优化的结果就是⽣成⼀个执⾏计划【EXPLAIN】,这个执⾏计划表明了应该使⽤哪些索引进⾏查询,表之间的连接顺序是啥样的…
存储引擎
- 也叫“表处理器”
- 物理上如何表示记录,怎么从表中读取数据,怎么把数据写⼊具体的物理存储器上,这都是存储引擎负责的事情
- MySQL提供了各式各样的存储引擎,不同存储引擎管理的表具体的存储结构可能不同,采⽤的存取算法也可能不同
- 各种不同的存储引擎向上边的MySQL server层【连接管理、查询缓存、语法解析、查询优化】提供统⼀的调⽤接⼝
MyISAM和InnoDB
PS:支持外键并不一定是好事,一般外键约束我们是不用的,因为不好维护、性能差。
表空间关系
InnoDB区
InnoDB页
InnoDB是⼀个将表中的数据存储到磁盘上的存储引擎。
处理数据的过程是发⽣在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写⼊或
修改请求的话,还需要把内存中的内容刷新到磁盘上。
读写磁盘的速度⾮常慢,和内存读写差了⼏个数量级
InnoDB采取的⽅式是:将数据划分为若⼲个⻚,以⻚作为磁盘和内存之间交互的基本单位,InnoDB中⻚的⼤⼩⼀般为 16 KB。也就是在⼀般情况下,⼀次最少从磁盘中读取16KB的内容到内存中,⼀次最少把内存中的16KB内容刷新到磁盘中。
InnoDB行格式
我们平时是以记录为单位来向表中插⼊数据的,这些记录在磁盘上的存放⽅式也被称为⾏格式或者记录格式。
InnoDB存储引擎到现在为⽌设计了4种不同类型的⾏格式,分别是Compact、Redundant、Dynamic和Compressed⾏格式
记录头信息:
聚簇索引
InnoDB的B+树索引的注意事项:
-
根页面位置万年不动
-
内节点(非叶子节点)中目录项记录的唯一性 【索引列值 + 主键 + 页号】
-
一个页面最少存储2条记录
B+树索引适⽤的条件
CREATE TABLE person_info(id INT NOT NULL auto_increment,name VARCHAR(100) NOT NULL,birthday DATE NOT NULL,phone_number CHAR(11) NOT NULL,country varchar(100) NOT NULL,PRIMARY KEY (id),KEY idx_name_birthday_phone_number (name,birthday, phone_number)
);
从图中可以看出,这个idx_name_birthday_phone_number索引对应的B+树中⻚⾯和记录的排序⽅式就是这样的:
- 先按照name列的值进⾏排序。
- 如果name列的值相同,则按照birthday列的值进⾏排序。
- 如果birthday列的值也相同,则按照phone_number的值进⾏排序。
1.全值匹配
搜索条件中的列和索引列⼀致:
SELECT * FROM person_info
WHERE name ='Ashburn' AND birthday ='1990-09-27' AND phone_number ='15123983239';
MySQL有查询优化器,会分析这些搜索条件并且按照可以使⽤的索引中列的顺序来决定先使⽤哪个搜索条件,后使⽤哪个搜索条件,所以下面这种写法也可以:
SELECT * FROM person_info
WHERE birthday ='1990-09-27' AND phone_number ='15123983239' AND name ='Ashburn';
2.匹配左边的列
SELECT * FROM person_info WHERE name ='Ashburn';
SELECT * FROM person_info WHERE name ='Ashburn' AND birthday ='1990-09-27';
如果我们想使⽤联合索引中尽可能多的列,搜索条件中的各个列必须是联合索引中从最左边连续的列。
如果这样:
SELECT * FROM person_info WHERE name ='Ashburn' AND phone_number ='15123983239';
只能⽤到name列的索引,birthday和phone_number的索引就⽤不上了,因为name值相同的记录先按照birthday的值进⾏排序,birthday值相同的记录才按照phone_number值进⾏排序。
3.匹配列前缀
对于字符串类型的索引列来说,我们只匹配它的前缀也是可以快速定位记录的
SELECT * FROM person_info WHERE name LIKE 'As%';
如果只给出后缀或者中间的某个字符串,⽐如这样:
SELECT * FROM person_info WHERE name LIKE '%As%';
MySQL就⽆法快速定位记录位置了,因为字符串中间有’As’的字符串并没有排好序,所以只能全表扫描了。
4.匹配范围值
所有记录都是按照索引列的值从⼩到⼤的顺序排好序的,所以这极⼤的⽅便我们查找索引列的值在某个范围内的记录
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow';
由于B+树中的数据⻚和记录是先按name列排序的,所以我们上边的查询过程其实是这样的:
- 找到name值为Asa的记录。
- 找到name值为Barlow的记录。
- 由于所有记录都是由链表连起来的(记录之间⽤单链表,数据⻚之间⽤双链表),所以他们之间的记录都可以很容易的取出来
- 找到这些记录的主键值,再到聚簇索引中回表查找完整的记录。
如果对多个列同时进⾏范围查找的话,只有对索引最左边的那个列进⾏范围查找的时候才能⽤到B+树索引
SELECT * FROM person_info
WHERE name > 'Asa' AND name < 'Barlow' AND birthday > '1980-01-01';
上边这个查询可以分成两个部分:
- 通过条件name > ‘Asa’ AND name < 'Barlow’来对name进⾏范围查找,查找的结果可能有多条name值不同的记录
- 对这些name值不同的记录继续通过birthday > '1980-01- 01’条件继续过滤。
这样⼦对于联合索引idx_name_birthday_phone_number来说, 只能⽤到name列的部分,⽽⽤不到birthday列的部分
因为只有name值相同的情况下才能⽤birthday列的值进⾏排序,⽽这个查询中通过name进⾏范围查找的记录中可能并不是按照 birthday 列进⾏排序的,所以在搜索条件中继续以birthday列进⾏查找时是⽤不到这个B+树索引的。
5.精确匹配某⼀列并范围匹配另外⼀列
对于同⼀个联合索引来说,虽然对多个列都进⾏范围查找时只能⽤到最左边那个索引列,但是如果左边的列是精确查找,则右边的列可以进⾏范围查找
SELECT * FROM person_infoWHERE name = 'Ashburn' AND birthday > '1980-01-01' AND birthday < '2000-12-31' AND phone_number > '15100000000';
由于name列是精确查找,所以通过name = 'Ashburn’条件查找后得到的结果的name值都是相同的,它们会再按照birthday的值进⾏排序。所以此时对 birthday 列进⾏范围查找是可以⽤到B+树索引的。
通过birthday的范围查找的记录的birthday的值可能不同,所以这个条件⽆法再利⽤B+树索引了
同理,下边的查询也是可能⽤到这个idx_name_birthday_phone_number联合索引的:
SELECT * FROM person_info
WHERE name = 'Ashburn'
AND birthday = '1980-01-01'
AND phone_number > '15100000000';
6.⽤于排序
如果ORDER BY⼦句⾥使⽤到了我们的索引列,就有可能省去在内存或⽂件中排序的步骤
SELECT * FROM person_info ORDER BY name,birthday, phone_number LIMIT 10;
这个查询的结果集需要先按照name值排序,如果记录的name值相同,则需要按照birthday来排序,如果birthday的值相同,则需要按照phone_number排序。这个B+树索引本身就是按照上述规则排好序的,所以直接从索引中提取数据,然后进⾏回表操作取出数据就好了
使⽤联合索引进⾏排序时,ORDER BY的⼦句后边的列的顺序也 必 须 按 照 索 引 列 的 顺 序 给 出 , 如 果 给 出 ORDER BY phone_number, birthday, name的顺序,那也是⽤不了B+树索引的
不可以使⽤索引进⾏排序的⼏种情况:
- ASC、DESC混⽤:规定使⽤联合索引的各个排序列的排序顺序必须是⼀致的。也就是要么各个列都是ASC规则排序,要么都是DESC规则排序。
- WHERE⼦句中出现⾮排序使⽤到的索引列:
SELECT * FROM person_info WHERE country = 'China' ORDER BY name LIMIT 10;
,这个查询只能先把符合搜索条件country = 'China’的记录提取出来后再进⾏排序,是使⽤不到索引的 - 排序列包含⾮同⼀个索引的列:有时候⽤来排序的多个列不是⼀个索引⾥的,这种情况也不能使⽤索引进⾏排序:
SELECT * FROM person_info ORDER BY name, country LIMIT 10;
- 排序列使⽤了复杂的表达式:要想使⽤索引进⾏排序操作,必须保证索引列是以单独列的形式出现,⽽不是修饰过的形式
SELECT * FROM person_info ORDER BY UPPER(name) LIMIT 10;
,使⽤了UPPER函数修饰过的列就不是单独的列啦,这样就⽆法使⽤索引进⾏排序。
7.⽤于分组
有时候我们为了⽅便统计表中的⼀些信息,会把表中的记录按照某些列进⾏分组。⽐如下边这个分组查询:
SELECT name, birthday, phone_number, COUNT(*) FROM person_infoGROUP BY name, birthday,phone_number
这个查询语句相当于做了3次分组操作:
- 先把记录按照name值进⾏分组,所有name值相同的记录划分为⼀组
- 将每个name值相同的分组⾥的记录再按照birthday的值进⾏分组,将birthday值相同的记录放到⼀个⼩分组⾥,所以看起来就像在⼀个⼤分组⾥⼜化分了好多⼩分组。
- 再将上⼀步中产⽣的⼩分组按照phone_number的值分成更⼩的分组
- 所以整体上看起来就像是先把记录分成⼀个⼤分组, 然后把⼤分组分成若⼲个⼩分组,然后把若⼲个⼩分组再细分成更多的⼩⼩分组。
- 然后针对那些⼩⼩分组进⾏统计
⽐如在我们这个查询语句中就是统计每个⼩⼩分组包含的记录条数。如果没有索引的话,这个分组过程全部需要在内存⾥实现,⽽如果有了索引的话,恰巧这个分组顺序⼜和我们的B+树中的索引列的顺序是⼀致的,⽽我们的B+树索引⼜是按照索引列排好序的,这不正好么,所以可以直接使⽤B+树索引进⾏分组。
回表
回表的代价
回表会使⽤到两个B+树索引,⼀个⼆级索引,⼀个聚簇索引。
访问⼆级索引使⽤顺序I/O,访问聚簇索引使⽤随机I/O。
需要回表的记录越多,使⽤⼆级索引的性能就越低
甚⾄让某些查询宁愿使⽤全表扫描也不使⽤⼆级索引。⽐⽅说name值在Asa~Barlow之间的⽤户记录数量占全部记录数量90%以上,那么如果使⽤idx_name_birthday_phone_number索引的话,有90%多的id值需要回表,这不是吃⼒不讨好么,还不如直接去扫描聚簇索引(也就是全表扫描)。
需要回表的记录数越多,就越倾向于使⽤全表扫描,反之倾向于使⽤⼆级索引 + 回表的⽅式。
限制查询获取较少的记录数会让优化器更倾向于选择使⽤⼆级索引 + 回表的⽅式进⾏查询,因为回表的记录越少,性能提升就越⾼
对于有排序需求的查询,上边讨论的采⽤全表扫描还是⼆级索引 + 回表的⽅式进⾏查询的条件也是成⽴的
所以查询的时候一般都带上limit
子句。
如何回表
⼀般情况下只能利⽤单个⼆级索引执⾏查询
SELECT * FROM single_tableWHERE key1 ='abc' AND key2 > 1000;
查询优化器会识别到这个查询中的两个搜索条件:
- key1 =‘abc’
- key2 > 1000
优化器⼀般会根据single_table表的统计数据来判断到底使⽤哪个条件
哪个扫描的⾏数会更少,就会选择那个扫描⾏数较少的条件
然后将从该⼆级索引中查询到的结果经过回表得到完整的⽤户记录后,再根据其余的WHERE条件过滤记录。
假设优化器决定使⽤idx_key1索引进⾏查询,那么整个查询过程可以分为两个步骤:
- 步骤1:使⽤⼆级索引定位记录的阶段,也就是根据条件key1='abc’从idx_key1索引代表的B+树中找到对应的⼆级索引记录。
- 步骤2:回表阶段,也就是根据上⼀步骤中找到的记录的主键值进⾏回表操作,也就是到聚簇索引中找到对应的完整的⽤户记录,再根据条件key2 > 1000到完整的⽤户记录继续过滤。将最终符合过滤条件的记录返回给⽤户。
覆盖索引
为了彻底告别回表操作带来的性能损耗,我们建议:最好在查询列表⾥只包含索引列
SELECT name, birthday, phone_number FROM person_info
WHERE name > 'Asa' AND name < 'Barlow'
因为我们只查询name, birthday, phone_number这三个索引列的值,所以在通过dx_name_birthday_phone_number
索引得到结果后就不必到聚簇索引中再查找记录的剩余列,也就是country列的值了,这样就省去了回表操作带来的性能损耗。我们把这种只需要⽤到索引的查询⽅式称为索引覆盖。
PS:不⿎励⽤*号作为查询列表,最好把我们需要查询的列依次标明
索引条件下推
SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';
其中的key1 > 'z’可以使⽤到索引,但是key1 LIKE '%a’却⽆法使⽤到索引,在以前版本的MySQL中,是按照下边步骤来执⾏这个查询的:
-
先根据key1 > 'z’这个条件,从⼆级索引idx_key1中获取到对应的⼆级索引记录。
-
根据上⼀步骤得到的⼆级索引记录中的主键值进⾏回表,找到完整的⽤户记录再检测该记录是否符合key1 LIKE '%a’这个条件,将符合条件的记录加⼊到最后的结果集。
但是虽然key1 LIKE '%a’不能组成范围区间参与range访问⽅法的执⾏,但这个条件毕竟只涉及到了key1列,所以设计MySQL的⼤叔把上边的步骤改进了⼀下:
- 先根据key1 > 'z’这个条件,定位到⼆级索引idx_key1中对应的⼆级索引记录。
- 对于指定的⼆级索引记录,先不着急回表,⽽是先检测⼀下该记录是否满⾜key1 LIKE '%a’这个条件,如果这个条件不满⾜,则该⼆级索引记录压根⼉就没必要回表。
- 对于满⾜key1 LIKE '%a’这个条件的⼆级索引记录执⾏回表操作。
回表操作其实是⼀个随机IO,⽐较耗时,所以上述修改虽然只改进了⼀点点,但是可以省去好多回表操作的成本。设计MySQL的⼤叔们把他们的这个改进称之为索引条件下推(英⽂名:Index Condition Pushdown)
如何挑选索引
只为⽤于搜索、排序或分组的列创建索引
也就是说,只为出现在WHERE⼦句中的列、连接⼦句中的连接列,或者出现在ORDER BY或GROUP BY⼦句中的列创建索引。
考虑列的基数
列的基数指的是某⼀列中不重复数据的个数,⽐⽅说某个列包含值2, 5, 8, 2, 5, 8, 2, 5, 8,虽然有9条记录,但该列的基数却是3。
也就是说,在记录⾏数⼀定的情况下,列的基数越⼤,该列中的值越分散;列的基数越⼩,该列中的值越集中。
这个列的基数指标⾮常重要,直接影响我们是否能有效的利⽤索引。假设某个列的基数为1,也就是所有记录在该列中的值都⼀样,那为该列建⽴索引是没有⽤的,因为所有值都⼀样就⽆法排序,⽆法进⾏快速查找了
⽽且如果某个建⽴了⼆级索引的列的重复值特别多,那么使⽤这个⼆级索引查出的记录还可能要做回表操作,这样性能损耗就更⼤了。
所以结论就是:最好为那些列的基数⼤的列建⽴索引,为基数太⼩列的建⽴索引效果可能不好。
索引列的类型尽量⼩
数据类型越⼩,在查询时进⾏的⽐较操作越快(这是CPU层次的)
数据类型越⼩,索引占⽤的存储空间就越少,在⼀个数据⻚内就可以放下更多的记录,从⽽减少磁盘I/O带来的性能损耗, 也就意味着可以把更多的数据⻚缓存在内存中,从⽽加快读写效率。
这个建议对于表的主键来说更加适⽤,因为不仅是聚簇索引中会存储主键值,其他所有的⼆级索引的节点处都会存储⼀份记录的主键值, 如果主键适⽤更⼩的数据类型,也就意味着节省更多的存储空间和更⾼效的I/O。
索引字符串值的前缀
KEY idx_name_birthday_phone_number (name(10), birthday, phone_number)
⼀个字符串其实是由若⼲个字符组成,如果我们在MySQL中使⽤utf8字符集去存储字符串的话,编码⼀个字符需要占⽤1~3个字节。
假设我们的字符串很⻓,那存储⼀个字符串就需要占⽤很⼤的存储空间。在我们需要为这个字符串列建⽴索引时,那就意味着在对应的B+树中有这么两个问题:
- B+树索引中的记录需要把该列的完整字符串存储起来,⽽且字符串越⻓,在索引中占⽤的存储空间越⼤。
- 如果B+树索引中索引列存储的字符串很⻓,那在做字符串⽐较时会占⽤更多的时间。
索引的设计者提出了个⽅案 — 只对字符串的前⼏个字符进⾏索引
也就是说在⼆级索引的记录中只保留字符串前⼏个字符。
这样在查找记录时虽然不能精确的定位到记录的位置,但是能定位到相应前缀所在的位置,然后根据前缀相同的记录的主键值回表查询完整的字符串值,再对⽐就好了。这样只在B+树中存储字符串的前⼏个字符的编码,既节约空间,⼜减少了字符串的⽐较时间,还⼤概能解决排序的问题,
索引列前缀对排序的影响:
SELECT * FROM person_info ORDER BY name LIMIT 10;
因为⼆级索引中不包含完整的name列信息,所以⽆法对前⼗个字符相同,后边的字符不同的记录进⾏排序,也就是使⽤索引列前缀的⽅式⽆法⽀持使⽤索引排序
让索引列在⽐较表达式中单独出现
假设表中有⼀个整数列my_col,我们为这个列建⽴了索引。下边的两个WHERE⼦句虽然语义是⼀致的,但是在效率上却有差别:
- WHERE my_col * 2 < 4
- WHERE my_col < 4/2
第1个WHERE⼦句中my_col列并不是以单独列的形式出现的,⽽是以my_col * 2这样的表达式的形式出现的,存储引擎会依次遍历所有的记录,计算这个表达式的值是不是⼩于4,所以这种情况下是使
⽤不到为my_col列建⽴的B+树索引的。
⽽第2个WHERE⼦句中my_col列并是以单独列的形式出现的,这样的情况可以直接使⽤B+树索引。
所以结论就是:如果索引列在⽐较表达式中不是以单独列的形式出 现,⽽是以某个表达式,或者函数调⽤形式出现的话,是⽤不到索引的。
主键插⼊顺序
我们知道,对于⼀个使⽤InnoDB存储引擎的表来说,在我们没有显式的创建索引时,表中的数据实际上都是存储在聚簇索引的叶⼦节点的。
⽽记录⼜是存储在数据⻚中的,数据⻚和记录⼜是按照记录主键值从⼩到⼤的顺序进⾏排序
所以如果我们插⼊的记录的主键值是依次增⼤的话,那我们每插满⼀个数据⻚就换到下⼀个数据⻚继续插,⽽如果我们插⼊的主键值忽⼤忽⼩的话,这就⽐较麻烦了
数据⻚已经满了啊,再插进来咋办呢?我们需要把当前⻚⾯分裂成两个⻚⾯,把本⻚中的⼀些记录移动到新创建的这个⻚中。⻚⾯分裂和记录移位意味着什么?意味着:性能损耗!
所以如果我们想尽量避免这样⽆谓的性能损耗,最好让插⼊的记录的主键值依次递增, 这样就不会发⽣这样的性能损耗了。
所以我们建议:让主键具有AUTO_INCREMENT
,让存储引擎⾃⼰为表⽣成主键,⽽不是我们⼿动插⼊
冗余和重复索引
通过idx_name_birthday_phone_number索引就可以对name列进⾏快速搜索,再创建⼀个专⻔针对name列的索引就算是⼀个冗余索引,维护这个索引只会增加维护的成本,并不会对搜索有什么好处。
另⼀种情况,我们可能会对某个列重复建⽴索引,⽐⽅说这样:
CREATE TABLE repeat_index_demo ( c1 INT PRIMARY KEY,c2 INT,UNIQUE uidx_c1 (c1), INDEX idx_c1 (c1));
c1既是主键、⼜给它定义为⼀个唯⼀索引,还给它定义了⼀个普通索引,可是主键本身就会⽣成聚簇索引,所以定义的唯⼀索引和普通索引是重复的,这种情况要避免。
单表访问方法
MySQL执⾏查询语句的⽅式称之为访问⽅法或者访问类型,⼤致分为下边两种:
- 使⽤全表扫描进⾏查询
- 使⽤索引进⾏查询
而使⽤索引来执⾏查询的⽅式五花⼋⻔,⼜可以细分为许多种类:
- 针对主键或唯⼀⼆级索引的等值查询
- 针对普通⼆级索引的等值查询
- 针对索引列的范围查询
- 直接扫描整个索引
例子:
CREATE TABLE single_table (id INT NOT NULL AUTO_INCREMENT,key1 VARCHAR(100),key2 INT,key3 VARCHAR(100),key_part1 VARCHAR(100),key_part2 VARCHAR(100),key_part3 VARCHAR(100),common_field VARCHAR(100),PRIMARY KEY (id),KEY idx_key1 (key1),UNIQUE KEY idx_key2 (key2),KEY idx_key3 (key3),KEY idx_key_part(key_part1, key_part2,key_part3)
) Engine=InnoDB CHARSET=utf8;
const
通过主键列来定位⼀条记录:这时,MySQL会直接利⽤主键值在聚簇索引中定位对应的⽤户记录
SELECT * FROM single_table WHERE id = 1438;
根据唯⼀⼆级索引列来定位⼀条记录
SELECT * FROM single_table WHERE key2 = 3841;
通过主键或者唯⼀⼆级索引列与常数的等值⽐较来定位⼀条记录是非常快的,把这种通过主键或
者唯⼀⼆级索引列来定位⼀条记录的访问⽅法定义为:const,意思是常数级别的,代价是可以忽略不计的
ref
对某个普通的⼆级索引列与常数进⾏等值⽐较:
SELECT * FROM single_table WHERE key1 = 'abc';
对于这个查询,我们当然可以选择全表扫描来逐⼀对⽐搜索条件是否满⾜要求,我们也可以先使⽤⼆级索引找到对应记录的id值,然后再回表到聚簇索引中查找完整的⽤户记录。
由于普通⼆级索引并不限制索引列值的唯⼀性,所以可能找到多条对应的记录,也就是说使⽤
⼆级索引来执⾏查询的代价取决于等值匹配到的⼆级索引记录条数。
如果匹配的记录较少,则回表的代价还是⽐较低的,所以MySQL可能选择使⽤索引⽽不是全表扫描的⽅式来执⾏查询。
把这种搜索条件为⼆级索引列与常数等值⽐较,采⽤⼆级索引来执⾏查询的访问⽅法称为:ref。
从图示中可以看出,对于普通的⼆级索引来说,通过索引列进⾏等值⽐较后可能匹配到多条连续的记录,⽽不是像主键或者唯⼀⼆级索引那样最多只能匹配1条记录
这种ref访问⽅法⽐const差了那么⼀丢丢,但是在⼆级索引等值⽐较时匹配的记录数较少时的效率还
是很⾼的
PS:不论是普通的⼆级索引,还是唯⼀⼆级索引,它们的索引列对包含NULL值的数量并不限制,所以我们采⽤key IS NULL这种形式的搜索条件最多只能使⽤ref的访问⽅法,⽽不是const的访问⽅法。
ref_or_null
有时候我们不仅想找出某个⼆级索引列的值等于某个常数的记录,还想把该列的值为NULL的记录也找出来:
SELECT * FROM single_demo WHERE key1 ='abc' OR key1 IS NULL;
当使⽤⼆级索引⽽不是全表扫描的⽅式执⾏该查询时,这种类型的查询使⽤的访问⽅法就称为ref_or_null,这个ref_or_null访问⽅法的执⾏过程如下:
range
利⽤索引进⾏范围匹配的访问⽅法称之为:range
SELECT * FROM single_table
WHERE key2 IN (1438,6328) OR (key2 >= 38 AND key2 <= 79);
index
当我们可以使⽤索引覆盖,但需要扫描全部的索引记录时,该表的访问⽅法就是index
SELECT key_part1, key_part2, key_part3 FROM single_tableWHERE key_part2 ='abc';
由于key_part2并不是联合索引idx_key_part最左索引列,所以我们⽆法使⽤ref或者range访问⽅法来执⾏这个语句。但是这个查询符合下边这两个条件:
- 它的查询列表只有3个列:key_part1, key_part2,key_part3,⽽索引idx_key_part⼜包含这三个列
- 搜索条件中只有key_part2列。这个列也包含在索引idx_key_part中
我们可以直接通过遍历idx_key_part索引的叶⼦节点的记录来⽐较key_part2 ='abc’这个条件是否成⽴
把匹配成功的⼆级索引记录的key_part1, key_part2, key_part3列的值直接加到结果集中就⾏了。
由于⼆级索引记录⽐聚簇索记录⼩的多(聚簇索引记录要存储所有⽤户定义的列以及所谓的隐藏列,⽽⼆级索引记录只需要存放索引列和主键)
⽽且这个过程也不⽤进⾏回表操作
所以直接遍历⼆级索引⽐直接遍历聚簇索引的成本要⼩很多
所以就把这种采⽤遍历⼆级索引记录的执⾏⽅式称之为:index。
all
最直接的查询执⾏⽅式就是我们已经提了⽆数遍的全表扫描,对于InnoDB表来说也就是直接扫描聚簇索引
把这种使⽤全表扫描执⾏查询的⽅式称之为:all。
system
当表中只有⼀条记录并且该表使⽤的存储引擎的统计数据是精确的,⽐如MyISAM、Memory,那么对该表的访问⽅法就是system
eq_ref
在连接查询时,如果被驱动表是通过主键或者唯⼀⼆级索引列等值匹配的⽅式进⾏访问的(如果该主键或者唯⼀⼆级索引是联合索引的话,所有的索引列都必须进⾏等值⽐较),则对该被驱动表的访问⽅法就是eq_ref