人人都会AI编程

24.1 SQL 编写规范:命名、格式、语法规范

更新时间:2026-07-11

写 SQL 和写代码一样,清晰一致的风格能大幅降低维护成本和出错概率。一个项目里,SQL 可能散落在持久层框架、脚本、报表查询、数据修复工单等各个角落,如果没有统一规范,很快就会变成谁也看不懂的“天书”。下面这些规范,不是学院派的教条,而是经过无数次联调排查、线上故障复盘后沉淀下来的实用准则。


24.1.1 命名规范

命名是规范中看起来最琐碎、却最能体现团队专业度的地方。好的命名能让后来者一眼看懂表里存的是什么、字段含义是什么,不用反复翻数据字典。

通用约定

  • 统一使用小写字母:MySQL 在 Linux 下默认对表名、库名大小写敏感(由 lower_case_table_names 参数控制),但在 Windows 下不敏感。为了避免跨平台迁移时出现诡异问题,一律用小写字母,用下划线分隔单词(如 user_order)。
  • 禁止使用保留字和关键字:不要给表、字段取名 orderkeystatusdesctable 等保留字。万一非用不可,需要用反引号括起来( order ),但这会让每次引用都多写两个符号,也不利于后续维护。
  • 使用有意义的英文单词或通用缩写:避免拼音、拼音缩拼(yhxx 到底是指“用户信息”还是“银行限额”?)、无意义前缀(t_, v_, f_ 等)。如果是团队内部广泛接受的极简缩写(如 uid 指 user id),可以酌情使用,但要在文档中统一说明。
  • 命名长度适中,不做过度的信息包含useruser_info_detail_table_1 好。过长字段名在编写联表 SQL 时会让别名显得十分臃肿。

数据库命名

-- 好:清晰表达库的用途
CREATE DATABASE blog;
CREATE DATABASE order_system;
-- 差:含义模糊、不通用
CREATE DATABASE db1;
CREATE DATABASE ceshi;

库名一般与项目、子系统的名称保持一致,用下划线连接。避免在库名中使用 - 或纯数字。

表命名

  • 表名用名词或名词短语,清晰表达所存实体。
  • 关联表可以用 主实体_从实体 的方式来命名,例如 user_rolearticle_tag。对于多对多关联表,通常将两个实体名组合,中间用下划线连接。
  • 有时会用前缀标识表的业务模块,但要保持项目内统一。比如财务模块用 fin_ 前缀、订单模块用 ord_
-- 好
user
product
order_item
article_comment
-- 差
data1
a_user
user_table

字段命名

  • 字段名应明确表达含义,并保证在整个数据库中风格一致。例如所有表的主键都命名为 id,外键用 关联表名_id(如 user_idproduct_id)。
  • 布尔类型字段建议用 is_has_ 前缀,如 is_deleted, has_children。自增主键直接用 id
  • 日期时间字段用 _at 结尾表示时间点(created_atupdated_at),用 _date 表示纯日期(如 birth_date)。
  • 避免彻底无意义的占位名称(col1, col2, temp)。
-- 好的字段命名
id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id     INT UNSIGNED NOT NULL,
title       VARCHAR(200) NOT NULL,
content     TEXT,
is_published TINYINT(1) DEFAULT 0,
created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

索引命名

索引名应该体现其类型和所包含的字段,让后续管理(尤其是用 EXPLAIN 分析或手动修改时)一目了然。

  • 普通索引:idx_字段名1_字段名2
  • 唯一索引:uk_字段名uk_字段名1_字段名2
  • 主键:使用 id 字段本身,索引自动命名为 PRIMARY(通常不需要额外指定名称)
CREATE INDEX idx_user_status ON user(status);
CREATE UNIQUE INDEX uk_user_email ON user(email);
CREATE INDEX idx_order_user_time ON order(user_id, created_at);

24.1.2 格式规范

SQL 语句虽然不需要像 Python 那样严格要求缩进,但良好的排版能极大提高可读性和可维护性。

关键字大写,其他小写

这是最通用的行业习惯,目的是让 SQL 的结构关键词从一堆表名、字段名中凸显出来:

SELECT id, name, created_at
FROM user
WHERE status = 1
ORDER BY created_at DESC
LIMIT 10;

如果团队更习惯全小写,也可以统一,但不要在同一个项目里混用大小写。

主要子句换行与缩进

  • SELECTFROMWHEREGROUP BYHAVINGORDER BYLIMIT 等关键字作为语句的首层结构,左对齐,独占一行。
  • JOINON 也独占一行,保持对齐。
  • AND / OR 条件通常放在 WHERE 下缩进,或者放在条件列的开头,两种都可以,但要统一。

推荐写法:

SELECT u.id, u.name, o.amount
FROM user u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.status = 1
  AND o.created_at >= '2025-01-01'
  AND o.amount > 100
ORDER BY o.created_at DESC
LIMIT 20;

避免过长的不换行语句

一行 SQL 写了二三百个字符,在终端或 IDE 里查看时不仅需要横向滚动,而且很难快速看出是取了哪些字段、有哪些条件。尤其不要把复杂的子查询全部堆在一行。适当换行才是方便后续 review 和调试。

统一使用单引号

SQL 标准中字符串使用单引号,MySQL 也允许双引号(在非 ANSI_QUOTES 模式下),但为了可移植性和规范,统一用单引号包裹字符串:

SELECT * FROM user WHERE name = 'Alice';

别名、标识符尽量用反引号括起来(仅在必要时),或者干脆不使用特殊字符,直接用小写字母。

别名使用 AS 关键词

虽然 SELECT name n 在 MySQL 中是合法的,但为了语意清晰,强烈建议显示地写上 AS

SELECT COUNT(*) AS total_users FROM user;

24.1.3 语法规范

良好的语法规范可以帮你避免很多隐性的 Bug 和低效查询。

1. 不要使用 SELECT *

在应用程序代码中,绝不要用 * 查询所有字段,除非是临时调试。

  • * 无法利用覆盖索引,会增加回表和网络传输量。
  • 一旦表结构增加字段,可能导致不期望的数据泄露,或代码对该字段的忽视引发错误。
  • 明确的字段列表让后来者一眼看清当前功能所需数据。
-- 避免
SELECT * FROM user WHERE id = 1;

-- 推荐
SELECT id, name, email FROM user WHERE id = 1;

2. 显式声明字段列表

INSERT 语句中,始终显式指定要插入的字段,不要依赖表结构的顺序:

-- 危险:如果表加了字段,这条语句立即出错或误插
INSERT INTO user VALUES (1, 'Alice', 'alice@example.com');

-- 正确:
INSERT INTO user (id, name, email) VALUES (1, 'Alice', 'alice@example.com');

这不仅安全,还能避免因表字段顺序和脚本不一致导致的静默数据错位。

3. 使用参数化查询,避免 SQL 注入

永远不要用拼接字符串的方式将用户输入嵌入到 SQL 中。所有主机语言都提供了预处理语句或 ORM 框架,它们会自动进行参数化处理。

// 错误示范
String sql = "SELECT * FROM user WHERE name = '" + userName + "'";

// 正确(JDBC PreparedStatement)
PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE name = ?");
ps.setString(1, userName);

即使是在 DBA 手工执行的脚本中,也要警惕变量替换,避免因特殊字符导致语法错误或数据破坏。

4. 用 IN 代替 OR,但不是无脑用

对于等值条件,IN 通常比一连串的 OR 更简洁,优化器也可能将其转换为范围扫描。但当值的数量过大(比如超过几百上千),性能可能下滑,此时可考虑临时表或改用多个查询。

-- 简洁
SELECT * FROM product WHERE category IN ('book', 'electronics', 'cloth');

5. 谨慎使用 DISTINCT 和 GROUP BY

DISTINCTGROUP BY 在很多场景下会触发额外的排序操作。如果你只是希望去掉重复行,先检查是否因为表关联产生了笛卡尔积导致多出重复数据,优化 JOIN 条件往往更根本。若确实要去重,在结果集不大的前提下使用没有问题。

6. 避免在 WHERE 子句中对字段进行函数操作或运算

这是最常见的索引失效原因之一:

-- 索引失效
SELECT * FROM orders WHERE YEAR(created_at) = 2025;

-- 走索引
SELECT * FROM orders WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01';

对等号右边的值进行计算则没问题,如 WHERE amount * 100 > 500

7. 合理使用 LIMIT,须同时搭配 ORDER BY

没有 ORDER BYLIMIT 返回的顺序是不确定的,尽管在 InnoDB 中一般是主键顺序,但这只是实现细节,随时可能变化。尤其在分页查询中,必须明确排序字段。

-- 永远不要这样
SELECT * FROM user LIMIT 10;
-- 需要明确
SELECT * FROM user ORDER BY id LIMIT 10;

*8. 使用 COUNT() 而不是 COUNT(列名)**

COUNT() 统计行数,COUNT(列名) 统计该列非 NULL 的行数。如果你只是想计算总行数,COUNT() 是最清晰且性能最佳的写法。InnoDB 会对 COUNT(*) 进行专门的优化(比如通过最小的二级索引统计),而 COUNT(非空列) 可能会让优化器采取不同的执行计划。

9. 合理使用 VARCHAR

  • 为字段选择足够但不浪费的长度,如用户名 VARCHAR(50) 通常足够,而不要随意设为 VARCHAR(255)
  • 若字段是定长且长度很小(如 2 位 state code),可使用 CHAR,效率更高。
  • 对于超长文本才使用 TEXT 类型,记住 TEXT 通常存放在外部溢出页,可能影响查询性能。

10. 善用 COMMENT

为表和字段添加注释,即使数据库本身不完全依赖注释,在团队协作和后续接手时也是一笔巨大财富。

CREATE TABLE user (
    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
    name VARCHAR(50) NOT NULL COMMENT '用户昵称',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1正常 0禁用',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) COMMENT '用户基础信息表';

11. 禁止在线上使用不带 WHERE 条件的 DELETE 和 UPDATE

这是写入规范里的安全底线。即使你确定要修改所有行,也应该写成 WHERE 1=1 显示提醒自己,并且执行前先用 SELECT 验证影响行数。不少数据库中间件甚至能拦截无 WHERE 的更新语句,提前保护数据。

最后,规范不是一成不变的,但混乱会导致代价。所以关键是在团队内部达成一致,形成文档,并且通过 Code Review 和静态检查工具(如 sqlint 等)来落实。一套好的 SQL 规范能节省的沟通和排错时间,远远超过你敲这些字花掉的几分钟。