常用MySQL优化

发布 : 2019-01-30 分类 : MySQL 浏览 :

1、SQL语句中IN包含的值不应过多

MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。
比如

1
SELECT id FROM t_test WHERE id IN(1,2,3);

对于这种连续的数值,能用BETWEEN就不要用IN,再或者使用连接来替换。

2、SELECT语句务必指明字段名称

SELECT * 增加很多不必要的消耗(CPU、IO、内存、网络带宽)增加了使用覆盖索引的可能性,当表结构发生改变时,字段也需要更新。所以要求直接在SELECT后面接上字段名。

3、当只需要一条数据的时候,使用 LIMIT 1

这是为了使用EXPLAINtype列达到const类型

4、如果排序字段没有用到索引,尽量少排序

5、如果限制条件中其他字段没有索引,尽量少用 OR

OR两边的字段中,如果有一个不是索引字段,而其他条件也不是索引字段,会造成该查询不走索引的情况。很多时候使用 UNION ALL或者是UNION(必要的时候)的方式来代替OR会得到更好的效果。

6、尽量用 UNION ALL 代替 UNION

UNIONUNION ALL的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,UNION ALL的前提条件是两个结果集没有重复数据。

7、不使用 ORDER BY RAND()

1
2
3
SELECT id FROM t_test ORDER BY RAND() LIMIT 1000;
优化为
SELECT id FROM t_test t1 JOIN (SELECT RAND()* (SELECT MAX(id) t_test) AS nid) t2 ON t1.id > t2.nid LIMIT 1000;

8、区分 INEXISTSNOT INNOT EXISTS

1
SELECT * FROM 表A WHERE id IN(SELECT id FROM 表B)

上面SQL语名相当于

1
SELECT * FROM 表A WHERE EXISTS(SELECT * FROM 表B WHERE 表B.id = 表A.id)

区分 INEXISTS 主要是造成了驱动顺序的改变(这是性能变化的关键),如果是 EXISTS,那么以外层表为驱动表,先被访问,如果是 IN ,那么先执行子查询。所以 IN 适合于外表大而内表小的情况,EXISTS 适合于外表小而内表大的情况。

关于 NOT INNOT EXISTS,推荐使用 NOT EXISTS,不仅仅是效率问题,NOT IN 可能存在逻辑问题。

高效写出一个替代 NOT EXISTS 的SQL语句。

原SQL语句

1
SELECT colname.... FROM 表A WHERE a.id NOT IN(SELECT b.id FROM 表B)

优化SQL语句

1
SELECT colname.... FROM 表A LEFT JOIN 表B ON WHERE a.id = b.id WHERE b.id IS NULL

表A不在表B中的数据

9、使用合理的分布方式以提高分布的效率

1
SELECT id,name,age FROM preson LIMIT 866331, 20

使用上述SQL语句做分页的时候,可能有人会发现,随着表数据量的增加,直接使用LIMIT分页查询会越来越慢。

优化的方法如下:可以取前一页的最大行数的id,然后根据这个最大的id来限制下一页的起点。比如此列中,上一页最大的id是866331。SQL可以采用如下的写法:

1
SELECT id,name,age FROM preson WHERE id > 866331 LIMIT 20

10、避免在 WHERE 子句中对字段进行 NULL 值判断

对于 NULL 的判断会导致引擎放弃使用索引而进行全表扫描。

11、不建议使用 % 前缀模糊查询

例如 LIKE "%name" 或者 LIKE "%name%",这种查询会导致索引失效而进行全表扫描。但是可以使用 LIKE "name%"

如何查询 %name%

创建全文索引SQL语法

1
ALTER TABLE `t_test` ADD FULLTEXT INDEX `idx_user_name`(`user_name`);

使用全文索引SQL语句

1
SELECT id,name,age FROM t_test WHERE MATCH(user_name) AGAINST("张三" IN BOOLEAN mode);

注意:在需要创建全文索引之前,请联系DBA确定能否创建,同 时需要注意的是查询语句的写法与普通索引的区别。

12、避免在 WHERE 子句中对字段进行表达式操作

1
SELECT id,name FROM t_test WHERE age * 22 = 36;

对字段运行了算术运算,会造成引擎放弃使用索引,建议改成

1
SELECT id,name FROM t_test WHERE age = 36 / 2;

13、避免隐匿类型转换

WHERE 子句中出现 COLUMN 字段的类型和传入的参数类型不一致的时候发生的类型转换,建议先确定WHERE 中的参数类型。

14、对于联合索引来说,要遵守最左前缀法则。

举列来说索引含有字段id、name、school,可以直接用id字段,也可以id、name这样的顺序,但是name;school都无法使用这个索引。所以在创建联合索引的时候一定要注意索引字段顺序,常用的查询字段放在最前面。

15、必要时可以使用 FORCE INDEX 来强制查询出某个索引

有的时候MySQL优化器采取它认为合适的索引来检索SQL语句,但是可能它所采用的索引并不是我们想要的。这时就可以采用FORCE INDEX来强制优化器使用我们制定的索引。

16、注意范围查询语句

对于联合索引来说,如果存在范围查询,比如 BETWEEN>< 等条件时,会造成后面的索引字段失效。

17、关于 JOIN优化

LEFT JOIN 以左边的表为驱动表,INNER JOIN 自动找出那个数据少的表作为驱动表,RIGHT JOIN 以右表为驱动表。

1、MySQL中淌有 FULL JOIN,可以用以下方式来解决

1
SELECT * FROM A LEFT JOIN B on B.name = A.name WHERE B.name is NULL UNION ALL SELECT * FROM B;

2、尽量使用 INNER JOIN,避免 LEFT JOIN
参与联合查询的表至少为2张表,一般都存在大小之分。如果连接方式是INNER JOIN,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表,但是LEFT JOIN在驱动表的选择上遵循的是左边驱动右边驱动的原则,即LEFT JOIN左边的表名为驱动表。
3、合理利用索引
被驱动表的索引字段作为 ON 的限制字段。
4、利用小表去驱动多大表
这样可以减少嵌套循环中的循环次数,以减少IO总量及CPU运算的次数。

5、巧用STRAIGHT_JOIN
INNER JOIN是由MySQL选择驱动表,但是有些特殊情况需要选择另个表作为驱动表,比如有GROUP BYORDER BY「Using filesort」「Using temporary」时。STRAIGHT_JOIN来强制连接顺序,在STRAIGHT_JOIN左边的表名就是驱动表,右边则是被驱动表。在使用STRAIGHT_JOIN有个前提条件是该查询是内连接,也就是INNER JOIN。其他链接不推荐使用STRAIGHT_JOIN,否则可能造成查询结果不准确。

最后感谢阅读,写得不对的地方请指教。

本文作者 : KYRIECAO
原文链接 : https://caozongpeng.github.io/2019/01/30/常用MySQL优化/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!

知识 & 情怀 | 二者兼得

微信扫一扫, 向我投食

微信扫一扫, 向我投食

支付宝扫一扫, 向我投食

支付宝扫一扫, 向我投食

留下足迹