执行计划分析
参数有:
-
possible_keys :可能用到的索引;
-
key:实际用的索引,如果这一项为 NULL,说明没有使用索引;
-
key_len:索引的长度;
-
rows:扫描的数据行数。
-
type:数据扫描类型,执行效率从低到高的顺序为:
- All:全表扫描
- index:全索引扫描,不再需要对数据进行排序,但是开销依然很大。
- range:索引范围扫描,使用 < 、>、in、between 等关键词,只检索给定范围的行。
- ref:非唯一索引扫描,或者是唯一索引的非唯一性前缀,返回数据返回可能是多条。
- eq_ref:唯一索引扫描,使用主键或唯一索引时产生的访问方式,通常使用在多表联查中。
- const:结果只有一条的主键或唯一索引扫描
-
extra
-
Using filesort :查询包含 group by 操作,而且无法利用索引完成排序操作,尽量避免。
-
Using temporary:用临时表保存中间结果,常见于order by 和 group by。效率低,要避免。
-
Using index:使用了覆盖索引,避免了回表操作,效率不错。
-
2000W魔咒
What?
单表数据量达到2000w左右,查询效率断崖下跌。
Why?
B+树深度越深,读盘次数越多,效率越低。下面计算2、3层B+树的数据量:
-
X:非页节点索引数
- File Header (38 byte)、Page Header (56 Byte)、Infimum + Supermum(26 byte)、File Trailer(8byte), 再加上页目录,大概 1k
- 页的大小是 16K, 剩下 15k 用于存数据
- 主键假设是 Bigint (8 byte), 页号也是固定的(4Byte), 那么索引页中的一条数据也就是 12byte。
- X=15*1024/12≈1280 行。
-
Y:页节点数据行树
- 假设一条行数据 1k ,那一页就能存下 15 条,Y = 15*1024/1000 ≈15。
-
Z:B+树高度,2或3
-
Total =x^(z-1) *y
- z = 2, Total = (1280 ^1 )*15 = 19200
- z = 3, Total = (1280 ^2) *15 = 24576000 (约 2450kw)
所以三层的极限差不多2000W,超过就变四层了。
count(*)、count(1)、count(字段)
按照性能排序:count(*)=count(1)>count(主键字段)>count(字段)
-
count():统计符合查询条件的记录中,函数指定的参数不为 NULL 的记录有多少个。
-
count(*)
- count(*) 其实等于 count(0)
-
count(1)
- InnoDB 循环遍历聚簇索引(主键索引),将读取到的记录返回给 server 层,但是不会读取记录中的任何字段的值,因为 count 函数的参数是 1,不是字段,所以不需要读取记录中的字段值。参数 1 很明显并不是 NULL,因此 server 层每从 InnoDB 读取到一条记录,就将 count 变量加 1。
- 二级索引时,使用key_len 最小的二级索引进行扫描。
-
count(主键字段)
- 只有主键索引:循环遍历聚簇索引,将读取到的记录返回给 server 层,然后读取记录中的 id 值,就会 id 值判断是否为 NULL,如果不为 NULL,就将 count 变量加 1。
- 二级索引时,InnoDB 循环遍历的对象是二级索引。
-
count(字段)
- 采用全表扫描的方式来计数,所以它的执行效率是比较差的。
-
为什么要通过遍历的方式来计数?
- MyISAM 引擎,执行 count 函数只需要 O(1 )复杂度,MyISAM 的数据表都有一个 meta 信息有存储了row_count值,由表级锁保证一致性,所以直接读取 row_count 值就是 count 函数的执行结果。
- InnoDB 存储引擎是支持事务的,同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的,所以无法像 MyISAM一样,只维护一个 row_count 变量。
如何优化 count(*)?
- 近似值:使用 show table status 或者 explain 命令来表进行估算。
- 额外表保存计数值:插入一条记录的同时,将计数表中的计数字段 + 1