这10个SQL写法正在杀死你的数据库!2025避坑指南
生产环境慢查询、CPU飙升、死锁频发?99%的问题都出在这10个低级错误上。
01. SELECT * —— 最温柔的“数据库杀手”
错误写法:
SELECT * FROM orders WHERE user_id = 12345;危害分析:
- 返回所有列,浪费IO和网络带宽
- 覆盖索引失效,被迫回表查询
- 数据量百万级时,单表查询可拖垮整个实例
正确案例:
SELECT id, order_no, amount, status FROM orders WHERE user_id = 12345;只取所需字段,让覆盖索引发挥作用,查询速度可提升10倍以上。
02. 无索引字段上的模糊查询
错误写法:
SELECT * FROM users WHERE phone LIKE '8%';危害分析:
- 前导通配符%导致索引完全失效
- 全表扫描,百万级数据耗时从毫秒级跌至秒级
- 并发量上来后,数据库连接池瞬间打满
正确案例:
-- 方案一:避免前导通配符SELECT * FROM users WHERE phone LIKE '138%';-- 方案二:使用全文索引SELECT * FROM users WHERE MATCH(phone) AGAINST('138' IN BOOLEAN MODE);03. 隐式类型转换
错误写法:
SELECT * FROM orders WHERE order_no = 123456;-- order_no字段类型为varchar危害分析:
- MySQL会将索引字段隐式转换,导致索引失效
- 执行计划显示type=ALL,全表扫描
- 这类错误在日志中很难直接发现
正确案例:
SELECT * FROM orders WHERE order_no = '123456';保持字段类型与查询条件类型一致,索引才能正常使用。
04. 在索引列上使用函数
错误写法:
SELECT * FROM orders WHERE DATE(create_time) = '2025-01-01';危害分析:
- 对索引列应用函数,索引形同虚设
- 即使create_time有索引,依然全表扫描
- 数据量大时,这条SQL可直接打爆CPU
正确案例:
SELECT * FROM orders WHERE create_time >= '2025-01-01' AND create_time < '2025-01-02';使用范围查询,让索引高效工作。
05. 分页查询偏移量过大
错误写法:
SELECT * FROM logs ORDER BY id LIMIT 1000000, 20;危害分析:
- MySQL需要扫描并跳过前100万条数据
- 越往后翻页越慢,甚至超时
- 大量无用的排序和回表操作
正确案例:
-- 方案一:记录上一页最大IDSELECT * FROM logs WHERE id > 1000000 ORDER BY id LIMIT 20;-- 方案二:覆盖索引+延迟关联SELECT l.* FROM logs lINNER JOIN ( SELECT id FROM logs ORDER BY id LIMIT 1000000, 20) tmp ON l.id = tmp.id;06. NOT IN 和 NOT EXISTS 滥用
错误写法:
SELECT * FROM orders WHERE user_id NOT IN (SELECT user_id FROM banned_users);危害分析:
- NOT IN遇到NULL值会直接返回空结果
- 子查询结果集大时,性能极差
- 无法有效利用索引
正确案例:
-- 使用NOT EXISTS替代SELECT o.* FROM orders oWHERE NOT EXISTS ( SELECT 1 FROM banned_users b WHERE b.user_id = o.user_id);-- 或使用LEFT JOIN + IS NULLSELECT o.* FROM orders oLEFT JOIN banned_users b ON o.user_id = b.user_idWHERE b.user_id IS NULL;07. OR条件导致索引失效
错误写法:
SELECT * FROM products WHERE category_id = 10 OR price < 100;危害分析:

- OR条件中只要有一个字段无索引,整个查询走全表扫描
- 多个索引无法合并使用时,优化器选择错误
正确案例:
-- 使用UNION拆分SELECT * FROM products WHERE category_id = 10UNION ALLSELECT * FROM products WHERE price < 100 AND category_id != 10; -- 避免重复08. 长事务未提交
错误写法:
BEGIN;SELECT * FROM orders WHERE id = 1 FOR UPDATE;-- 业务逻辑处理,耗时5秒UPDATE orders SET status = 2 WHERE id = 1;-- 忘记COMMIT或延迟COMMIT危害分析:
- 锁资源长时间不释放,阻塞其他事务
- 导致死锁频发,连接池耗尽
- MVCC版本链过长,影响查询性能
正确案例:
-- 缩短事务时间,及时提交BEGIN;SELECT * FROM orders WHERE id = 1 FOR UPDATE;-- 快速执行业务逻辑UPDATE orders SET status = 2 WHERE id = 1;COMMIT; -- 立即提交-- 或设置超时时间SET innodb_lock_wait_timeout = 5;09. 批量操作逐条执行
错误写法:
// 循环执行单条插入for (Order order : orderList) { jdbcTemplate.execute("INSERT INTO orders ...");}危害分析:
- 每次执行都产生一次网络往返
- 频繁提交日志,磁盘IO飙升
- 1000条数据耗时可能超过10秒
正确案例:
-- 批量插入INSERT INTO orders (order_no, user_id, amount) VALUES ('ORD001', 1, 100),('ORD002', 1, 200),('ORD003', 2, 150);一条SQL处理1000条数据,性能提升50倍以上。
10. 错误的JOIN顺序
错误写法:
SELECT * FROM large_table ltLEFT JOIN small_table st ON lt.id = st.id;-- large_table: 1000万行,small_table: 100行危害分析:
- 驱动表选择错误,导致扫描大量数据
- 临时表过大,内存不够写入磁盘
- 执行时间从毫秒级恶化到分钟级
正确案例:
-- 小表驱动大表SELECT * FROM small_table stLEFT JOIN large_table lt ON st.id = lt.idWHERE st.status = 1;-- 使用STRAIGHT_JOIN强制指定驱动顺序SELECT STRAIGHT_JOIN * FROM small_table stLEFT JOIN large_table lt ON st.id = lt.id;优化器有时会选错,手动干预执行计划可大幅提升性能。
写在最后
这10个错误写法,每一个都曾在生产环境中真实地“杀”过数据库。SQL优化没有银弹,但遵循这些基本原则,足以应对99%的性能问题。
核心原则速记:
- 精准查询,拒绝SELECT *
- 索引友好,避免函数和隐式转换
- 小表驱动大表
- 批量操作减少交互
- 事务短小精悍
检查你的代码库,这10个坑你踩过几个?欢迎在评论区留言讨论。
— 让每一行SQL都高效运行 —
文章版权声明:除非注明,否则均为边学边练网络文章,版权归原作者所有