MySQL 常用优化方式
- sql 书写顺序与执行顺序
- SQL设计优化
- 使用索引
- 避免索引失效
- 分析慢查询
- 合理使用子查询和临时表
- 列相关使用
- 日常SQL优化场景
- limit语句
- 隐式类型转换
- 嵌套子查询
- 混合排序
- 查询重写
sql 书写顺序与执行顺序
(7) SELECT
(8) DISTINCT <select_list>
(1) FROM <main_table>
(3) <join_type> JOIN <join_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) HAVING <having_condition>
(9) ORDER BY <order_by_condition>
(10) LIMIT <limit_number>
SQL设计优化
使用索引
- 确保对经常作为查询条件的列创建索引
- 对JOIN的列创建索引
- 但要注意不要过度索引,因为这会减慢写操作(如INSERT、UPDATE、DELETE)。
避免索引失效
- 匹配前缀:如果在WHERE子句中使用LIKE操作符,且匹配模式的开始部分是通配符(例如LIKE ‘%xyz’),将不会使用索引。但如果是’xyz%',则使用索引。
- 使用函数或表达式:在列上使用函数或表达式(例如WHERE YEAR(column) = 2021)会导致索引失效,因为MySQL无法利用索引直接定位数据
- OR条件:or表达式两边都必须有索引才会走索引,否则将不会走索引。
- 反向条件不走索引 != 、 <> 、 NOT IN、IS NOT NULL
- 数据类型不一致,隐式转换(可能)导致索引失效【这点在隐式类型转换中有场景演示】
分析慢查询
- 使用
EXPLAIN
关键字可以帮助你分析SQL查询的执行计划。通过分析,你可以发现潜在的性能瓶颈,如全表扫描、没有使用索引等问题。
合理使用子查询和临时表
- 子查询和临时表如果不当使用,会造成性能问题。在可能的情况下,尝试使用JOIN来替代它们。
列相关使用
-
使用最适合数据的最小数据类型,如INT、VARCHAR等,这可以减少磁盘IO,提高查询效率。
-
尽量避免使用
SELECT *
,而是明确指定需要查询的字段。这不仅可以减少数据传输量,还能提高查询效率。
日常SQL优化场景
limit语句
SELECT *
FROM operation
WHERE type = 'SQLStats'
AND name = 'SlowLog'
ORDER BY create_time
LIMIT 1000, 10;
在优化上面SQL时,如果数据量特别庞大,除了在type, name, create_time 字段上加组合索引,还可以记录上一次返回列表最后一条数据,以它为开始,优化后(并不会根据数据量的增长而发生变化):
SELECT *
FROM operation
WHERE type = 'SQLStats'
AND name = 'SlowLog'
AND create_time > '2017-03-16 14:00:00'
ORDER BY create_time limit 10;
隐式类型转换
隐式转换,就是不带转换类型的转换,当一个字段类型为varchar,但是在判断时SQL是用int去判断,MySQL 就会对这个int进行隐式转换,将其int类型转换为varchar
-- salecode 为varchar类型
explain select * from my_distribute where salecode=898
在上述例子中,salecode为varchar类型,其列有索引,但是SQL并没有使用索引,是因为SQL中发生了隐式转换,导致了全表扫描,那是不是所有隐式转换都会使索引失效?
-- address 为int类型
explain select * from my_distribute where address='22'
还是同一个表,address类型为int,其列有索引,但是SQL却使用索引[address],以上可知,隐式转换不一定会导致索引失效,而是根据索引的类型变化,如果是数值类型,则右边无论是数值还是字符串都可以走索引,但是我们在开发中,一定要格外注意,避免隐式转换索引失效
嵌套子查询
UPDATE operation o
SET status = 'applying'
WHERE o.id IN (SELECT id
FROM (SELECT o.id,
o.status
FROM operation o
WHERE o.group = 123
AND o.status NOT IN ( 'done' )
ORDER BY o.parent,
o.id
LIMIT 1) t
);
上述例子中,更新operation使用了子查询去做过滤,并且使用了in条件,子查询将会在检索operation每一条数据时,都会执行一遍子查询,并将结果集返回判断operation的o.id是否在结果集中,效率非常低下,我们在开发中,也尽量使用join去替代子查询,改良后的sql:
UPDATE operation o
JOIN (SELECT o.id,
o.status
FROM operation o
WHERE o.group = 123
AND o.status NOT IN ( 'done' )
ORDER BY o.parent,
o.id
LIMIT 1) t
ON o.id = t.id
SET status = 'applying'
混合排序
MySQL 不能利用索引进行混合排序。但在某些场景,还是有机会使用特殊方法提升性能的。
SELECT *
FROM my_order o
INNER JOIN my_appraise a ON a.orderid = o.id
ORDER BY a.is_reply ASC,
a.appraise_time DESC
LIMIT 0, 20
由于 is_reply 只有0和1两种状态,可以按照下面的方法重写:
SELECT *
FROM ((SELECT *
FROM my_order o
INNER JOIN my_appraise a
ON a.orderid = o.id
AND is_reply = 0
ORDER BY appraise_time DESC
LIMIT 0, 20)
UNION ALL
(SELECT *
FROM my_order o
INNER JOIN my_appraise a
ON a.orderid = o.id
AND is_reply = 1
ORDER BY appraise_time DESC
LIMIT 0, 20)) t
ORDER BY is_reply ASC,
appraisetime DESC
LIMIT 20;
使用表子查询,将两个查询结果集UNION ALL 合并结果实现排序
查询重写
SELECT
a.*,
c.allocated
FROM
(
SELECT resourceid
FROM my_distribute d
WHERE isdelete = 0 AND cusmanagercode = '22353'
ORDER BY salecode LIMIT 20
) a
LEFT JOIN (
SELECT resourcesid, sum( ifnull( allocated, 0 )* 12345 ) allocated
FROM my_resources GROUP BY resourcesid
) c
ON a.resourceid = c.resourcesid
以上SQL中因为c表使用了全表聚合,导致了数据全表扫描10w数据,优化后:
SELECT
r.resourcesid,
sum( ifnull( allocated, 0 ) * 12345 ) allocated
FROM
my_resources r,
(
SELECT resourceid, cusmanagercode
FROM my_distribute d
WHERE isdelete = 0 AND cusmanagercode = '22353'
ORDER BY salecode LIMIT 20
) a
WHERE
r.resourcesid = a.resourceid
GROUP BY
resourceid