8 个最容易写错的 SQL 习惯,正在拖慢并毁掉你的数据库
很多数据库问题,并不是“高并发架构”这种听起来很吓人的大场面搞出来的。
真正把库拖慢、把业务搞崩、把同事逼疯的,往往是一些看起来没什么问题、实际上非常致命的 SQL 习惯。
这些写法在测试环境里也许还能跑,在数据量小的时候也许没感觉,但一旦表变大、业务变复杂、并发上来,代价就会立刻显现:慢查询、锁表、索引失效、脏数据、误删、甚至直接事故。
今天就来聊 8 个最常见、也最容易被忽视的坏习惯。
如果你平时写 SQL,这篇建议你对照着自查一遍。
SELECT *这是最常见的“偷懒型写法”。
SELECT *
FROM orders
WHERE user_id = 1001;看上去很方便,但问题不少:
更稳妥的写法是:只查你真正需要的字段。
SELECT order_id, order_no, total_amount, created_at
FROM orders
WHERE user_id = 1001;因为数据库的性能问题,很多时候不是“算得太慢”,而是“搬得太多”。
你以为自己只是多拿了几列,数据库看到的是:
一句话: 是小项目的朋友,大表时代的敌人。
WHERE 条件对字段做函数运算很多人写条件时,喜欢直接对列做处理:
SELECT *
FROM orders
WHERE DATE(created_at) = '2026-03-26';这类写法语义上没错,但性能上很容易出事。
因为当你对索引列做函数计算时,数据库往往没法直接利用索引,最后就可能退化成全表扫描。
更好的写法是改成范围查询:
SELECT order_id, user_id, created_at
FROM orders
WHERE created_at >= '2026-03-26 00:00:00'
AND created_at < '2026-03-27 00:00:00';尽量别动列,去动常量。
也就是说,别让数据库先把每一行都加工一遍,再比较;而是把边界算好,直接去命中索引。
这类错误在以下场景尤其常见:
DATE(create_time)YEAR(order_time)SUBSTRING(phone, 1, 3)UPPER(name)看着优雅,跑起来经常像灾难片。
%比如:
SELECT *
FROM users
WHERE name LIKE '%周%';这类查询在搜索场景很常见,但一旦前面加了 %,普通索引通常就很难用了。
结果就是:扫全表,然后一行一行比。
如果你的表有几百万数据,这种写法就不再是“能跑”,而是“谁写的,出来挨打”。
要看业务目标:
SELECT user_id, name
FROM users
WHERE name LIKE '周%';这种更有机会走索引。
如果你真的是要“包含某关键词”,别硬拿普通 LIKE '%xxx%' 扛所有搜索需求。
更合理的方向通常是:
数据库不是不能搜,但你别拿它当全文检索引擎往死里打。
IN、OR、联表条件乱写,结果把执行计划写崩了很多 SQL 慢,不是因为数据库不够强,而是因为你把优化器逼疯了。
例如:
SELECT *
FROM orders
WHERE status = 'PAID'
OR user_id = 1001;再比如:
SELECT *
FROM orders
WHERE id IN (1,2,3,4,5,...一大串...);或者联表时条件不清晰:
SELECT *
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.created_at >= '2026-01-01';表面看都合法,实际上可能导致:
IN 值,考虑临时表 / 批量表驱动OR 条件复杂时,考虑拆成 UNION ALL例如把 OR 拆开:
SELECT order_id, user_id, status
FROM orders
WHERE status = 'PAID'
UNION ALL
SELECT order_id, user_id, status
FROM orders
WHERE user_id = 1001
AND status <> 'PAID';不一定每次都要这么写,但你至少得知道:
复杂条件不是不能写,而是不能闭着眼乱写。
LIMIT 100000, 20这也是经典事故现场。
SELECT *
FROM orders
ORDER BY id
LIMIT 100000, 20;在前期数据少的时候没什么感觉,数据一多就开始痛了。
原因很简单:数据库不是直接跳到第 100000 行给你拿 20 条,它通常需要先扫描/排序前面大量记录,再丢掉它们。
页码越深,越慢。
SELECT order_id, id, created_at
FROM orders
WHERE id > 100000
ORDER BY id
LIMIT 20;如果按时间 + ID 排序,也可以这样:
SELECT order_id, created_at
FROM orders
WHERE (created_at, id) > ('2026-03-26 08:00:00', 100000)
ORDER BY created_at, id
LIMIT 20;可以,但要有边界:
一旦是开放式列表、大数据量、用户不断下拉,继续死磕深分页,就是在给数据库加班。
UPDATE / DELETE 不带条件,或者条件写得过于自信这个不用解释太多,懂的都疼。
DELETE FROM orders;或者:
UPDATE users
SET status = 'inactive';有些人会说:“我不是不加条件,我只是条件写错了。”
那效果也没什么本质区别。
SELECT *
FROM users
WHERE last_login_at < '2025-01-01';确认影响范围没问题,再执行:
UPDATE users
SET status = 'inactive'
WHERE last_login_at < '2025-01-01';DELETE FROM logs
WHERE created_at < '2024-01-01'
LIMIT 1000;尤其是生产环境,别把自己当赌神。
凡是会改数据的 SQL,执行前都问自己一句:
“如果我这句条件写错了,最坏会死多少数据?”
你会立刻冷静很多。
不少团队一遇到慢 SQL,就开始下意识加索引。
这思路和“头疼就把头砍了”差不多,主打一个直接。
问题是,索引不是免费午餐。
索引会带来:
例如下面这种:
(user_id)(user_id, status)(user_id, status, created_at)很多时候你以为自己很细致,实际上是在堆垃圾。
一句话:
索引是手术刀,不是护身符。
这是最致命的习惯。
很多人优化 SQL 的方式是这样的:
问题是,数据库不看你的感觉,只看执行计划。
以 MySQL 为例,先跑:
EXPLAIN
SELECT order_id, user_id, total_amount
FROM orders
WHERE user_id = 1001
AND created_at >= '2026-03-01 00:00:00';重点关注:
type:访问类型是不是太差key:到底用了哪个索引rows:预估扫描多少行Extra:有没有 Using filesort、Using temporary如果连执行计划都不看,就开始“优化”,那基本等于:
闭着眼给数据库做手术。
勇气可嘉,结果一般不太行。
把今天这 8 条收一下:
SELECT * 随手乱用%OR / IN / 联表条件乱写LIMIT offset, sizeUPDATE / DELETE 过于自信这些问题有一个共同点:
平时不痛,一出事就很痛。
而且最可怕的是,它们几乎都不是“不会 SQL”造成的,反而经常出现在“会一点、写得很顺手”的阶段。
真正靠谱的 SQL 开发习惯,不是能把语法写对,而是:
阅读原文:原文链接