本文介绍一些优化 MySQL 索引设计和查询的建议。在进行优化工做前,请务必了解MySQL EXPLAIN命令: 查看执行计划html
索引在逻辑上是指从索引列(关键字)到数据的映射,经过索引能够快速的由关键字查找到数据记录。顺序查找复杂度为O(n), 树状索引查找复杂度为O(logn), 哈希索引为O(1)。算法
MySQL中的索引通常是指BTree索引, InnoDB存储引擎使用B+树来实现BTree索引。sql
BTree索引保持数据之间的顺序,能够极大的加快精确搜索(=, in)、范围搜索(<,>), 去重(DISTINCT), 排序(ORDER BY) 和 聚合(GROUP BY)。数据库
总结来讲使用索引有三个优势:函数
由于写入数据时须要为新行创建索引,因此索引会减慢写入速度。请尽可能避免建立无用的索引。oop
示例:性能
SELECT * FROM `user` WHERE `id`=5; -- 可使用索引 SELECT * FROM `user` WHERE `id` + 1 = 5; -- 索引列做为表达式一部分时没法使用索引 SELECT * FROM `user` WHERE MD5(first_name)='MD5'; -- 索引列做为函数参数时没法使用索引
BTree索引具备最左匹配性质, 即只能按照索引列的顺序自左向右搜索,不能跳过索引列。优化
联合索引中存在范围查询(<, >, like, between) 会致使后面的索引列失效。设计
定义表和索引:code
CREATE TABLE `user` ( `id` INT, `first_name` VARCHAR(16), `middle_name` VARCHAR(16), `last_name` VARCHAR(16), PRIMARY KEY (`id`), KEY `idx_name` (`first_name`, `middle_name`, `last_name`) );
示例:
SELECT * FROM `user` WHERE `first_name`='a'; -- 可使用 idx_name 索引 SELECT * FROM `user` WHERE `first_name`='a' AND `middle_name`='b'; -- 可使用 idx_name 索引 SELECT * FROM `user` WHERE `first_name`='a' AND `middle_name`='b' AND `last_name`='c'; -- 可使用 idx_name 索引 SELECT * FROM `user` WHERE `middle_name`='b'; -- 不能使用 idx_name 索引 SELECT * FROM `user` WHERE `middle_name`='b' AND `last_name`='c'; -- 不能使用 idx_name 索引 SELECT * FROM `user` WHERE `first_name`='a' AND `last_name`='c'; -- 不能使用 idx_name 索引
上文中说的"可使用索引"是指能够用ref
,eq_ref
或 range
方式进行查询。
使用 EXPLAIN 命令查看3个不能使用索引示例的执行计划,能够发现 type 字段为 index, 这是在索引树上进行顺序查找。虽然性能优于全表扫描, 但比 ref 和 range 查询来讲要慢不少。
索引列为字符串等类型时, 可使用索引列的前缀字符串进行模糊查询
select * from user where first_name = 'abc' AND middle_name like 'de%';
这条语句的类型的为 range, 即在索引列上进行范围查询。
将联合索引理解为: 将索引列(关键字)按顺序拼接, 把拼接后的关键字与数据创建映射。最左匹配便是使用关键字前缀缩小搜索范围。
在进行多列搜索时有一条经验法则: 首先使用选择性高的列进行搜索。
咱们能够将选择性定义为 count(distinct ) / count(*), 也就是说知足条件的数据越少,则条件的选择性越高。
假设用户名name比性别gender选择性高, 那么查询应该写做WHERE name='finley' AND gender='M'
而不是WHERE gender='M' AND name='finley'
。
实际上两条语句是等效的, 当存在多个查询条件时 MySQL 优化器会根据索引和选择性决定最优的过滤顺序。
为每个列单独创建索引,并不能有效支持多列查询
CREATE TABLE `user` ( `id` INT, `first_name` VARCHAR(16), `middle_name` VARCHAR(16), `last_name` VARCHAR(16), PRIMARY KEY (`id`), KEY `idx_first_name` (`first_name`), KEY `idx_middle_name` (`middle_name`) );
查询语句:
select * from user where first_name = 'a' AND middle_name = 'bc';
查看查询计划:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | user | ALL | idx_first_name,idx_middle_name | NULL | NULL | NULL | 4 | 100.00 | Using where |
根据最左匹配法则和优先使用高选择性列的经验法则,能够得出一条建议:
对于须要进行多列查询的表,应创建包含全部参与查询列的联合索引, 索引的顺序应按照列的选择性从强到弱排列
一般在使用索引检索到数据以后,须要访问磁盘上数据表文件读取所须要的列,这种操做称为"回表"。
若索引中包含查询的全部列,则不须要回表操做直接从索引文件中读取数据便可, 这种索引称为覆盖索引。
在查询时尽可能减小"SELECT *"只查询须要的行, 条件容许时尽可能创建覆盖索引。
《数据库索引设计与优化》一书中提出了判断最佳索引的"三星索引"概念:
MySQL 在索引包含 null 的列时须要额外的开销, 尽可能避免容许索引列上存在 null。
除非有很是严格的一致性要求,不然应避免使用外键。
关于主键:
在查询较多且业务容许的状况下, 推荐使用自增主键。
不知道放哪儿好的两条建议:
MySQL在执行多表查询时能够采用Nest Loop Join算法,即选择数据集较小的一张表(数据集)做为驱动表, 遍历驱动表中全部记录并链接另外一张表中符合条件的记录。
在使用 JOIN 进行查询时 MySQL 会自动选择数据集较小的一张表做为驱动表。
LEFT JOIN 强制左表做为驱动表, RIGHT JOIN 则强制选择右表做为驱动表。
MySQL 的 STRAIGHT_JOIN 结果与 INNER JOIN 相同, 但强制使用左表做为驱动表, 可用来分析选择不一样驱动表的效果。
在业务容许的状况下, 让 MySQL 自行决定驱动表。
在使用 IN 进行多表查询时通常会把 IN 内部的嵌套循环做为驱动表, 应尽可能减小IN数据集的大小。实际上, MySQL 也会对 IN 和 EXISTS 查询进行优化, 选择最优的驱动表。