SQL代码审查指南:命名规范+10大反模式+四维检查清单,一篇全搞定

简介: 数据库小学妹带你攻克SQL规范难题!从命名、格式到10大反模式(如SELECT*、隐式转换、ORDER BY RAND等),结合真实踩坑案例,详解可读、可维护、高性能的SQL写法,并提供SQL Review四维审查清单与团队落地方法,助你写出工业级质量SQL。

📌 今日关键词:SQL 规范、代码审查、反模式、命名规范、SQL Review

大家好,我是 数据库小学妹 👋

之前我们讲过慢查询诊断,把慢SQL改好后能跑通,但在后期维护上却犯了难!

刚转行那会儿,我写的 SQL 长这样:

SELECT a.*, b.name, c.tt 
FROM u a, o b, p c 
WHERE a.id=b.uid AND b.sid=c.id AND a.st=1

功能没错,跑得也挺快。但同事看了半天,问我三个问题:

  1. a、b、c 分别是什么表?
  2. tt 和 st 代表什么意思?
  3. 为什么不用 JOIN 而是隐式连接?

我当场石化。其实这也很多新手都会踩的坑,SQL不是能跑起来就行,能跑的SQL和好维护的SQL之间还藏着一个SQL规范。这篇小学妹就把在SQL规范上踩过的坑跟大家分享一下,帮你少走弯路少踩坑!


一、为什么需要规范?

一个场景感受一下

上周你写了一条 SQL,这周产品改需求,你打开代码一看:

SELECT a.x, b.y, a.z FROM t1 a, t2 b WHERE a.id=b.tid AND a.st=1 AND b.tp='A'

你盯着看了 5 分钟,然后默默打开了 git blame——发现是自己写的。

这就是没规范的下场:连自己写的东西都不认识。

规范的 SQL 应该是什么样?

SELECT 
    orders.order_id,
    orders.amount,
    customers.customer_name
FROM orders
INNER JOIN customers ON orders.customer_id = customers.id
WHERE orders.status = 'confirmed'
  AND customers.type = 'vip';

一眼看过去:操作什么表、关联逻辑、筛选条件——全清楚。


二、命名规范:别让名字成为阅读理解题

2.1 表名

❌ 坏习惯:

-- 缩写到看不懂
SELECT * FROM u;      -- user? url? upload?
SELECT * FROM ord;    -- orders? ordinary?
SELECT * FROM t1;     -- 请问 t1 是什么?

✅ 规范写法:

-- 复数、下划线分隔、见名知义
SELECT * FROM users;
SELECT * FROM orders;
SELECT * FROM order_items;

2.2 字段名

❌ 坏习惯:

-- 单字母、拼音、缩写
SELECT uid, unm, pwd, crt_tm FROM users;

✅ 规范写法:

-- 完整单词、下划线分隔
SELECT user_id, username, password_hash, created_at FROM users;

2.3 别名

❌ 坏习惯:

-- 无意义的单字母
SELECT a.name, b.total FROM users a JOIN orders b ON a.id = b.uid;

✅ 规范写法:

-- 有意义的缩写
SELECT u.name, o.total 
FROM users u 
JOIN orders o ON u.id = o.user_id;

2.4 统一命名风格

类型 规范 示例
表名 复数、小写、下划线 users、order_items
字段名 单数、小写、下划线 created_at、updated_by
主键 id 或 表名_id id、user_id
外键 关联表_关联字段 order_id、customer_id
布尔字段 is_ 前缀 is_active、is_deleted
时间字段 _at 后缀 created_at、deleted_at
索引名 idx_表名_字段名 idx_users_phone
唯一索引 uk_表名_字段名 uk_users_email

三、格式规范:排版好,bug 少

3.1 关键字大写,表名字段名小写

-- ❌ 全部小写,混在一起
select name,email,created_at from users where status=1 order by created_at desc;

-- ✅ 关键字大写,一眼能看到结构
SELECT name, email, created_at
FROM users
WHERE status = 1
ORDER BY created_at DESC;

3.2 每个子句单独一行

-- ❌ 挤在一行
SELECT u.name, o.total, o.created_at FROM users u JOIN orders o ON u.id=o.user_id WHERE o.status='done' AND o.total>100 ORDER BY o.created_at DESC LIMIT 20;

-- ✅ 结构清晰
SELECT u.name, o.total, o.created_at
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'done'
  AND o.total > 100
ORDER BY o.created_at DESC
LIMIT 20;

3.3 逗号放行首

-- ✅ 逗号放前面,方便增删字段
SELECT
    user_id
  , username
  , email
  , created_at
  , updated_at
FROM users;

这种写法有两个好处:

  1. 新增字段只加一行,不会因为上个字段忘加逗号报错
  2. 注释某一行不用处理逗号

3.4 JOIN 必须指定类型,ON 条件紧跟其后

-- ❌ 隐式连接 + WHERE
SELECT u.name, o.total FROM users u, orders o WHERE u.id = o.user_id;

-- ✅ 显式 JOIN,逻辑清晰
SELECT u.name, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

四、10 大常见反模式:写完就提桶跑路

反模式 1:SELECT *

-- ❌ 坏
SELECT * FROM users WHERE id = 1;

-- ✅ 好
SELECT id, username, email FROM users WHERE id = 1;

为什么不行?

  • 表加了字段,你的结果集就变了——代码崩了你都不知道为什么
  • 不需要的字段也要传输,浪费 IO 和内存
  • 覆盖索引失效(本来查两个字段走索引就够,SELECT * 可能回表)

反模式 2:隐式类型转换

-- ❌ 坏:phone 是 VARCHAR,传入的是数字
SELECT * FROM users WHERE phone = 13800138000;

-- ✅ 好
SELECT * FROM users WHERE phone = '13800138000';

隐式转换会让索引失效。之前监控那篇提到过"Select_scan 增多",很多时候就是这个问题。

反模式 3:COUNT(column) vs COUNT(*)

-- ❌ 误区:以为 COUNT(1) 比 COUNT(*) 快
SELECT COUNT(1) FROM users;

-- ✅ COUNT(*) 和 COUNT(1) 在 MySQL 8.0 性能一样
-- 区别在这:
SELECT COUNT(col) FROM users;    -- 不统计 NULL
SELECT COUNT(*) FROM users;       -- 统计所有行,包括 NULL

MySQL 8.0 对 COUNT() 做了优化,它会选择短那条二级索引来计数。COUNT(col) 反而要多一个判断是否为 NULL 的步骤。没有特殊需求,直接用 COUNT()。

反模式 4:WHERE 条件中做函数操作

-- ❌ 坏:索引失效!
SELECT * FROM orders WHERE DATE(created_at) = '2026-05-23';

-- ✅ 好:让字段保持裸状态
SELECT * FROM orders 
WHERE created_at >= '2026-05-23 00:00:00' 
  AND created_at < '2026-05-24 00:00:00';

反模式 5:负向查询

-- ❌ 坏:NOT IN、NOT LIKE、!=、<> 通常不走索引
SELECT * FROM users WHERE status != 'deleted';

-- ✅ 好:用 IN 枚举正向条件
SELECT * FROM users WHERE status IN ('active', 'pending', 'frozen');

反模式 6:ORDER BY RAND()

-- ❌ 坏:全表扫描 + 文件排序,数据量大直接跪
SELECT * FROM articles ORDER BY RAND() LIMIT 10;

-- ✅ 好:应用层随机,或取最大 ID 后随机
SELECT MAX(id) INTO @max_id FROM articles;
SELECT * FROM articles WHERE id >= FLOOR(RAND() * @max_id) LIMIT 10;

反模式 7:LIKE '%keyword%' 前缀模糊

-- ❌ 坏:左模糊不走索引
SELECT * FROM articles WHERE title LIKE '%MySQL%';

-- ✅ 好:用全文索引
ALTER TABLE articles ADD FULLTEXT INDEX ft_title (title);
SELECT * FROM articles WHERE MATCH(title) AGAINST('MySQL' IN NATURAL LANGUAGE MODE);

反模式 8:大范围 LIMIT 分页

-- ❌ 坏:OFFSET 1000000 需要扫描前 100 万行
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;

-- ✅ 好:基于上一次查询的最大 ID
SELECT * FROM orders 
WHERE id > 1000000 
ORDER BY id LIMIT 20;

反模式 9:无脑使用 LEFT JOIN

-- ❌ 坏:不需要外联的场景用了 LEFT JOIN
SELECT u.*, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.status = 'done';    -- 这个 WHERE 直接把 LEFT JOIN 退化成 INNER JOIN

-- ✅ 好:明确逻辑,该用什么用什么
SELECT u.*, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.status = 'done';

LEFT JOIN 保留左表全部行。但你在 WHERE 里过滤右表字段,等于告诉数据库右表为 NULL 的行我不要——这和 INNER JOIN 逻辑一样,但优化器可能选错执行计划。

反模式 10:字段值与 NULL 的比较

-- ❌ 错:NULL 不能用 =、!= 比较
SELECT * FROM users WHERE deleted_at = NULL;     -- 永远返回空
SELECT * FROM users WHERE deleted_at != NULL;    -- 也永远返回空

-- ✅ 对
SELECT * FROM users WHERE deleted_at IS NULL;
SELECT * FROM users WHERE deleted_at IS NOT NULL;

NULL 是"不知道",不是"等于空"。NULL = NULL 返回的不是 TRUE,是 UNKNOWN。这是 SQL 新手最容易掉的一个坑。


五、SQL Review 审查清单

![[images/SQL审查四维清单.svg]]

每次提交 SQL 代码前,对着这个清单过一遍:

正确性检查

  • [ ] 查询逻辑是否符合需求?
  • [ ] JOIN 类型是否正确(INNER/LEFT/RIGHT)?
  • [ ] WHERE 条件是否遗漏(特别是软删除标记 deleted_at IS NULL)?
  • [ ] NULL 值的处理是否正确?

性能检查

  • [ ] 有没有 SELECT *?
  • [ ] WHERE 条件中的字段有没有索引?
  • [ ] 有没有在 WHERE 里对字段做函数操作?
  • [ ] 有没有隐式类型转换?
  • [ ] JOIN 的关联字段有没有索引?
  • [ ] 大表分页有没有用对方式?
  • [ ] 有没有不必要的排序(ORDER BY)或分组(GROUP BY)?

规范检查

  • [ ] 命名是否清晰(表名、字段名、别名)?
  • [ ] 格式是否统一(关键字大写、每个子句换行)?
  • [ ] 复杂逻辑有没有注释?
  • [ ] 有没有硬编码的魔法数字或日期?

数据安全

  • [ ] UPDATE / DELETE 有没有 LIMIT 保护?(防止一波误删)
  • [ ] 有没有 WHERE 条件?(防止全表更新)
-- ✅ 安全的写法
UPDATE orders SET status = 'cancelled' 
WHERE id = 12345 
LIMIT 1;  -- 只更新一行,多一行算我输

六、怎么在团队落地?

规范写得再漂亮,落不了地等于白写。分享一下我们团队推规范的方法:

Step 1:先搞一个"最小规范"

别一上来搞 100 条规则,没人看得完。先推 10 条最容易犯的:

  1. 禁止 SELECT *
  2. 显式 JOIN,不要隐式连接
  3. 表名、字段名用下划线命名
  4. 每个子句单独一行
  5. 关键字大写
  6. WHERE 中不对字段做函数操作
  7. UPDATE/DELETE 必须有 WHERE
  8. 避免大范围 OFFSET
  9. NULL 用 IS NULL 判断
  10. 复杂 SQL 加注释

Step 2:把规范放进 Code Review

提交代码时,Reviewer 对着上面那 10 条看。一开始可能会慢一点,但两周后大家都形成肌肉记忆了。

Step 3:收集"血泪案例"

团队里谁因为规范问题出了事故——记下来。下次有人质疑"为什么这么麻烦",直接把案例甩过去。

我自己的收藏:

同事 A 写 UPDATE orders SET status = 'done' 忘了加 WHERE,全表 500 万条订单状态被改。还好有备份,恢复了整整一下午。

Step 4:上工具辅助

手动查总有遗漏,可以用工具兜底:

  • SQLFluff:SQL 代码格式化工具,开源免费
  • SonarQube:代码质量平台,有 SQL 检测规则
  • Inception / Yearning:SQL 审核平台,提交前自动审查
# SQLFluff 使用示例
pip install sqlfluff
sqlfluff lint my_query.sql       # 检查规范
sqlfluff fix my_query.sql        # 自动格式化

七、今日学习心得

  1. "能跑"和"能维护"是两码事——三个月后你自己也看不懂自己写的 SQL
  2. 命名规范是最便宜的注释——名字起好了,注释都可以少写
  3. 10 大反模式里,SELECT * 和隐式转换是最容易出事的两个
  4. SQL Review 清单对着查,养成习惯就不会漏
  5. 规范推落地要小步快跑——先 10 条,再用工具,别一口气搞 100 条

👋 我是 数据库小学妹 一个用设计师思维学数据库的转行人。我们一起,把 SQL 写出工业级质量!💕

本文示例基于 MySQL 8.0 + InnoDB。

相关文章
|
4月前
|
SQL 监控 关系型数据库
数据库三大日志深度解析:Redo Log、Binlog、Undo Log 如何守护你的数据
本文由“数据库小学妹”带你厘清MySQL三大核心日志:Redo Log(引擎层物理日志,保障crash-safe)、Undo Log(支撑回滚与MVCC)和Binlog(Server层逻辑日志,用于复制与恢复),详解WAL机制与两阶段提交原理,助你真正理解事务安全底层逻辑。
|
4月前
|
人工智能 数据可视化 机器人
5个硬性指标:海外仓跨境呼叫中心选型,区域分配准确率98%+背后的技术能力盘点
本文深度解析海外仓跨境客服选型困境与方案,聚焦多渠道统一接入、区域/经理精准分配(≥98%)、AI对接物流接口、全链路质控、高稳定性(SLA≥99.9%)五大硬指标,横向对比合力亿捷等5家主流厂商,为中大型海外仓企业提供可落地的选型决策依据。(239字)
215 0
|
4月前
|
人工智能 JSON 运维
低成本AI编程新方案:DeepSeek V4-Pro接入Claude Code实战配置流程、评测与使用指南
在AI编程工具普及的当下,Claude Code凭借强大的代码理解、工程自动化、多技能调用能力,成为开发者日常开发、项目重构、自动化运维的必备终端工具。但长期使用官方原生模型,调用费用偏高,高频开发场景下成本压力十分明显。因此寻找一款接口兼容、推理能力相当、资费更低的替代模型,成为众多开发者的刚需。
944 0
|
4月前
|
存储 运维 关系型数据库
分布式数据库架构演进:从集中式到分布式,三大路线一次讲清楚
本文深入浅出解析分布式数据库选型逻辑:厘清集中式与分布式适用边界,剖析分片、事务、一致性三大技术难点,对比分库分表、原生分布式、共享存储集群三类架构。务实提醒——分布式非银弹,够用才是硬道理。
|
4月前
|
存储 关系型数据库 MySQL
MySQL索引底层原理:B+树能存多少数据?页分裂与回表机制详解
数据库小学妹带你深入B+树底层:为何选它而非二叉树或哈希?揭秘页分裂/合并机制、聚簇与二级索引差异、回表代价及磁盘I/O优化逻辑。3层B+树可存约800万数据,查询仅需3次I/O!
|
6月前
|
SQL 关系型数据库 MySQL
事务:让数据操作"要么全成功,要么全失败"!|转行学DB第10天
数据库小学妹带你轻松掌握事务!转账不丢钱、数据不混乱的秘密,就在ACID四大特性——原子性(全做或全不做)、一致性、隔离性、持久性。用START TRANSACTION开启,COMMIT提交,ROLLBACK回滚,保障多步操作安全可靠。
|
4月前
|
canal 关系型数据库 MySQL
MySQL LIKE查询太慢?手把手搭建Elasticsearch站内搜索
本文详解MySQL模糊搜索性能瓶颈及Elasticsearch全文检索解决方案:剖析`LIKE &#39;%关键词%&#39;`全表扫描原理,对比MySQL全文索引局限,深入讲解倒排索引机制,并实战演示Logstash/Canal数据同步、IK中文分词、高亮搜索等核心环节,助你构建毫秒级站内搜索。(239字)
|
4月前
|
关系型数据库 API 数据库
App Inventor接入Supabase:开源免费的后端新选择
App Inventor开发者福音!Supabase是开源Firebase替代品,基于PostgreSQL,支持实时订阅、自动API、文件存储与认证,可自部署、免费额度足。对比CloudBase,更适配海外场景与高可控需求,零服务器即可接入专业后端。(239字)
467 3
|
4月前
|
弹性计算 人工智能 API
零代码搭建AI智能体:阿里云Hermes Agent部署与百炼Token Plan配置完整手册
Hermes Agent是一款内置学习循环的开源AI智能体,具备自我进化能力,可通过自然语言调用浏览器、文件系统、代码编辑器等工具,完成文档处理、数据采集、代码开发等复杂任务,是个人与团队提升工作效率的核心工具。2026年,阿里云提供轻量应用服务器、ECS云服务器、无影云电脑三种主流部署方案,搭配官方预装镜像,大幅降低部署门槛;同时,阿里云百炼推出Token Plan团队版,提供固定月费、额度内无限调用的模型服务,与Hermes Agent深度集成,实现稳定、低成本的AI能力支撑。
476 1
|
4月前
|
弹性计算 人工智能 网络安全
阿里云轻量/ECS部署OpenClaw新手保姆级教程:从0到1搭建专属AI助理全流程
OpenClaw(原Moltbot/Clawdbot)是一款开源的AI代理自动化平台,能通过自然语言调用浏览器、文件系统、远程服务器等工具,完成文档整理、数据采集、任务调度等自动化工作,是个人与团队提升效率的得力助手。2026年,阿里云提供轻量应用服务器、ECS云服务器两种主流部署方案,搭配官方预装镜像,彻底解决新手环境配置复杂、依赖冲突等难题,零基础用户也能快速搭建7×24小时稳定运行的OpenClaw服务。
283 1