mysql经典语句
上周帮同事排查一个慢查询,经句他写了条 SELECT * FROM orders WHERE YEAR(created_at) = 2023,典语跑了整整12秒。经句我让他改成 WHERE created_at >= '2023-01-01' AND created_at < '2024-01-01',典语瞬间降到0.3秒。经句这事挺典型——很多人写SQL时压根没想过函数会废掉索引。典语
MySQL里最容易被低估的经句语句,其实是典语 EXPLAIN。你写完一条查询,经句前面加上它,典语看 type 列。经句如果是典语 ALL,说明全表扫描,经句数据量一大就完蛋。典语我见过一个日活十万的经句应用,订单表三百万行,开发人员还在用 LIKE '%关键词%' 做搜索,type 永远是 ALL。后来加了全文索引才缓过来。别迷信 LIKE 左边带百分号还能走索引,MySQL 8.0 之前想都别想。
JOIN 的写法也有讲究。有人喜欢 SELECT * FROM a, b WHERE a.id = b.a_id,老语法,能跑,但可读性差。更隐蔽的坑是 LEFT JOIN 后加 WHERE b.status = 1,这会把左连接变成内连接——因为过滤条件把 b 表为 NULL 的行全干掉了。要过滤右表但保留左表所有行,得把条件写在 ON 后面。我调试过一个报表查询,业务方说“左表数据怎么少了”,查了半天就是这个原因。
说到 LIMIT,深分页是另一个重灾区。LIMIT 1000000, 20 会先扫描前一百万行再丢掉,慢得离谱。优化办法是用游标:WHERE id > 上次最大id ORDER BY id LIMIT 20。前提是 id 有索引且连续。如果业务必须跳页,那就得接受性能损耗,或者用覆盖索引加子查询先拿主键。
更新语句更得小心。UPDATE users SET status = 0 WHERE last_login < '2023-01-01',看着没问题?如果 last_login 没索引,整张表被锁住,线上直接卡死。我习惯在 UPDATE 前先用 SELECT COUNT(*) 跑一遍同样的 WHERE,确认走索引且影响行数可控。还有 DELETE 不带 WHERE 的惨案——某初创公司运维手抖执行了 DELETE FROM logs,没加条件,binlog 恢复花了四小时。后来他们强制要求所有删除必须带 WHERE 且先 SELECT 验证。
插入数据时,INSERT INTO ... VALUES (...), (...), (...) 批量写比单条循环快十倍不止。但别一次塞几万条,max_allowed_packet 会爆。我一般每批500到1000条。还有 INSERT IGNORE 和 ON DUPLICATE KEY UPDATE,前者静默跳过冲突,后者做 upsert。注意 INSERT IGNORE 会忽略所有错误,包括数据类型不匹配,可能埋雷。
子查询尽量改成 JOIN。MySQL 5.6 之前子查询性能极差,现在虽然优化器聪明了,但复杂子查询还是容易生成糟糕的执行计划。比如 SELECT * FROM t1 WHERE id IN (SELECT id FROM t2 WHERE ...),如果 t2 结果集大,不如直接 JOIN。不过 EXISTS 在某些场景下比 IN 快,尤其是外层表小、内层表大的时候。
最后说个观察:很多团队禁用 SELECT *,理由是网络传输和内存开销。这没错,但更关键的是——SELECT * 会让覆盖索引失效。你明明建了 (a, b, c) 的联合索引,查询只需要 a 和 b,写 SELECT a, b 就能直接从索引拿数据,不用回表。写 SELECT * 就得回表读整行。数据量大了,差距是数量级的。
SQL 写得好不好,不看花哨,看 EXPLAIN 里的 rows 和 type。养成习惯:上线前把每条新查询扔进 EXPLAIN 看一眼。省下的不只是数据库CPU,还有你半夜被叫起来救火的时间。
(责任编辑:焦点)














