SQL 语句开发规范:打造高效数据库操作的基石

📌 引言

在项目开发中,SQL 作为与数据库交互的核心语言,其编写质量直接影响系统性能、数据安全性和代码可维护性。缺乏规范的 SQL 语句可能导致查询效率低下、数据不一致、甚至引发安全漏洞(如 SQL 注入)。

本文档从命名、格式、语句类型、性能、事务、安全等维度,制定统一的 SQL 开发规范,旨在帮助开发人员编写高效、稳健、易维护的 SQL 代码,保障项目数据库层的稳定性与可扩展性。

一、一般原则(基础规范)

1.1 命名规范(核心:见名知意,统一风格)

命名是代码可读性的基础,需遵循以下规则:

对象类型命名规则示例禁忌⚠️
数据库小写字母,用_分隔,前缀为项目缩写edu_student_db(教育项目学生库)避免大写、特殊字符(如#)、保留字(如user)
表小写字母,_分隔,前缀为业务模块(如user_、order_),单数形式user_info(用户信息表)、order_detail(订单详情表)避免无意义命名(如table1)、中文
列小写字母,_分隔,体现字段含义,避免冗余(如表名已含user,列无需user_id)id(主键)、create_time(创建时间)避免使用name(需明确为user_name)、desc(保留字)
索引前缀区分类型:idx_(普通索引)、uniq_(唯一索引)、pk_(主键),后接字段idx_user_phone(用户手机号索引)、uniq_user_idcard(身份证唯一索引)避免索引名与表名重复
存储过程 / 函数前缀区分类型:sp_(存储过程)、fn_(函数),后接业务逻辑sp_user_register(用户注册存储过程)、fn_calculate_order_amount(计算订单金额函数)避免无业务含义的命名

✅ 推荐:命名长度控制在 30 字符内,避免使用拼音(如

yonghu

),优先使用英文(如

user

)。

1.2 代码格式(核心:统一缩进,清晰易读)

格式混乱的 SQL 会大幅增加维护成本,需遵循以下规则:

1.2.1 关键字大小写
  • 统一使用大写(如SELECT、FROM、WHERE),区分关键字与业务字段,提升可读性。

  • ❌ 错误示例:select id, name from user_info where age > 18

  • ✅ 正确示例:SELECT id, name FROM user_info WHERE age > 18

1.2.2 缩进与换行
  • 使用4 个空格缩进(避免 Tab,防止不同编辑器显示不一致)。

  • 多表关联、子查询、条件判断时,按逻辑换行,每个子句单独占一行。

  • ❌ 错误示例:SELECT ``u.id``, o.order_no FROM user_info u JOIN order_info o ON ``u.id`` = o.user_id WHERE u.age > 18 AND o.create_time > '2024-01-01'

  • ✅ 正确示例:

SELECT 
    u.id, 
    o.order_no 
FROM 
    user_info u
JOIN 
    order_info o 
ON 
    u.id = o.user_id  -- 关联条件单独换行
WHERE 
    u.age > 18 
    AND o.create_time > '2024-01-01';  -- 多条件分行,便于注释和修改
1.2.3 空格与符号
  • 运算符(=、>、<、+等)前后加 1 个空格,避免紧凑写法。

    • ❌ u.age>18 → ✅ u.age > 18
  • 逗号(,)后加 1 个空格,前不加空格。

    • ❌ u.id``,o.order_no → ✅ u.id``, o.order_no
  • 分号(;)作为语句结束符,必须添加(避免多语句执行时歧义)。

1.2.4 注释规范
  • 单行注释:使用-- 注释内容(--后加 1 个空格),用于解释局部逻辑。

  • 多行注释:使用/* 注释内容 */,用于解释存储过程、函数的整体功能。

  • 注释位置:关键逻辑(如复杂条件、关联规则)必须加注释,避免 “后人猜逻辑”。

示例:

-- 筛选18岁以上用户的2024年订单(业务需求:仅统计成年用户的有效订单)
SELECT 
    u.id, 
    o.order_no 
FROM 
    user_info u
JOIN 
    order_info o 
ON 
    u.id = o.user_id  -- 关联用户与订单(用户ID为外键)
WHERE 
    u.age > 18 
    AND o.create_time > '2024-01-01'
    AND o.status = 1;  -- 1:订单已支付(状态枚举:0-待支付,1-已支付,2-已取消)

二、各类 SQL 语句规范(实操重点)

2.1 SELECT 语句(查询操作)

2.1.1 禁止使用SELECT *(核心原则)
  • ❌ 错误:SELECT * FROM user_info WHERE id = 1;

  • ✅ 正确:SELECT id, user_name, phone FROM user_info WHERE id = 1;

原因:

  1. 减少无效数据传输(如无需查询的password、remark字段),提升网络效率;

  2. 避免表结构变更(如新增字段)导致代码异常(如 VO 映射失败);

  3. 明确查询字段,便于后续维护(无需查表结构即可知道返回内容)。

2.1.2 别名使用规范
  • 表别名:使用1-2 个字母(基于表名首字母,如user_info→u,order_info→o),避免长别名。

  • 列别名:使用有意义的名称,避免中文,若需空格需用双引号(如"user name")。

示例:

-- 表别名:u(user_info)、o(order_info);列别名:order_count(订单数量)
SELECT 
    u.user_name, 
    COUNT(o.id) AS order_count  -- AS可省略,但建议保留(清晰区分字段与别名)
FROM 
    user_info u
LEFT JOIN 
    order_info o 
ON 
    u.id = o.user_id
GROUP BY 
    u.user_name;
2.1.3 JOIN 操作规范
  • 优先使用显式 JOIN(INNER JOIN、LEFT JOIN),避免隐式 JOIN(逗号分隔表,条件写在WHERE)。

    • ❌ 隐式 JOIN:SELECT * FROM user_info u, order_info o WHERE ``u.id`` = o.user_id;

    • ✅ 显式 JOIN:SELECT * FROM user_info u INNER JOIN order_info o ON ``u.id`` = o.user_id;

  • 左连接(LEFT JOIN)需注意:右表字段可能为NULL,需用IFNULL或COALESCE处理。

    • 示例:SELECT u.user_name, IFNULL(o.order_no, '无订单') AS order_no FROM user_info u LEFT JOIN order_info o ON ``u.id`` = o.user_id;
  • 禁止使用RIGHT JOIN(逻辑易混淆,可用LEFT JOIN反向关联替代)。

2.1.4 子查询规范
  • 子查询需用括号包裹,且优先使用EXISTS替代IN(大数据量下性能更优)。

    • ❌ SELECT * FROM user_info WHERE id IN (SELECT user_id FROM order_info WHERE status = 1);

    • ✅ SELECT * FROM user_info u WHERE EXISTS (SELECT 1 FROM order_info o WHERE o.user_id = u.id AND o.status = 1);

  • 子查询嵌套不超过2 层(超过 2 层需拆分为 JOIN 或分步查询,避免性能损耗)。

    • ❌ 3 层嵌套:SELECT * FROM a WHERE id IN (SELECT a_id FROM b WHERE b_id IN (SELECT id FROM c WHERE status = 1));

    • ✅ 拆分 JOIN:SELECT a.* FROM a JOIN b ON a.id = b.a_id JOIN c ON b.b_id = c.id WHERE c.status = 1;

2.2 INSERT 语句(插入操作)

2.2.1 必须指定列名
  • ❌ 错误:INSERT INTO user_info VALUES (1, '张三', '13800138000');(表结构变更后必错)

  • ✅ 正确:INSERT INTO user_info (id, user_name, phone) VALUES (1, '张三', '13800138000');

2.2.2 批量插入优化
  • 单条INSERT语句批量插入(避免循环执行多条INSERT,减少数据库连接开销)。
INSERT INTO user_info (id, user_name, phone) 
VALUES 
    (1, '张三', '13800138000'),
    (2, '李四', '13900139000'),
    (3, '王五', '13700137000');  -- 批量插入3条数据,逗号分隔
  • 示例(MySQL):
  • 批量插入条数控制在1000 条以内(避免单条语句过长,导致执行超时)。
2.2.3 特殊值处理
  • 日期时间:使用标准格式(如'2024-05-20 14:30:00'),避免依赖数据库默认格式。

  • 空值:明确使用NULL(而非空字符串'',除非业务允许),如INSERT INTO user_info (id, email) VALUES (1, NULL);

2.3 UPDATE 语句(更新操作)

2.3.1 必须加WHERE条件(重中之重)
  • ⚠️ 严禁无WHERE的更新(会导致全表数据被修改)!

  • ❌ 错误:UPDATE user_info SET phone = '13800138000';

  • ✅ 正确:UPDATE user_info SET phone = '13800138000' WHERE id = 1;

2.3.2 条件字段优先用索引
  • 更新条件字段(如WHERE id = 1中的id)需有索引,避免全表扫描(尤其大表)。

  • 示例:UPDATE order_info SET status = 2 WHERE order_no = '20240520001';(order_no需建索引)

2.3.3 避免更新大表全量数据
  • 若需更新大表(10 万 + 数据),需分批更新(如按id分段),避免锁表导致业务阻塞。
-- 第1批:id < 10000
UPDATE order_info SET status = 2 WHERE status = 1 AND id < 10000;
-- 第2批:10000 ≤ id < 20000
UPDATE order_info SET status = 2 WHERE status = 1 AND id BETWEEN 10000 AND 19999;
  • 示例(分批更新状态):

2.4 DELETE 语句(删除操作)

2.4.1 必须加WHERE条件(同 UPDATE)
  • ⚠️ 严禁无WHERE的删除(会导致全表数据丢失,且难以恢复)!

  • ❌ 错误:DELETE FROM user_info;

  • ✅ 正确:DELETE FROM user_info WHERE id = 1;

2.4.2 优先用LIMIT限制删除条数
  • 即使有WHERE条件,也建议用LIMIT避免误删过多数据(尤其条件判断错误时)。

    • 示例:DELETE FROM order_info WHERE create_time < '2023-01-01' LIMIT 1000;(每次删 1000 条,分批执行)
2.4.3 避免直接删除(推荐逻辑删除)
  • 业务数据优先使用逻辑删除(新增is_deleted字段,0- 未删除,1- 已删除),而非物理删除(DELETE)。

    • 示例:UPDATE user_info SET is_deleted = 1 WHERE id = 1;(逻辑删除)
  • 物理删除仅用于临时表、日志表等非核心数据,且删除前需备份。

三、性能优化规范(核心目标:提升查询效率)

3.1 索引使用规范

3.1.1 索引创建原则
  • 优先在查询条件字段(WHERE)、关联字段(JOIN ON)、排序字段(ORDER BY)上创建索引。

  • 避免过度索引:一张表索引不超过5 个(索引会加速查询,但会减慢INSERT/UPDATE/DELETE,因需维护索引结构)。

  • 联合索引遵循 “最左前缀原则”:如idx_age_name (age, name),仅WHERE age = 18或WHERE age = 18 AND name = '张三'会命中索引,WHERE name = '张三'不会命中。

3.1.2 禁止索引失效操作

以下操作会导致索引失效,需避免:

  1. 索引字段用函数:WHERE SUBSTR(phone, 1, 3) = '138'(改用WHERE phone LIKE '138%');

  2. 索引字段类型转换:WHERE id = '1'(id为 int 类型,应写WHERE id = 1);

  3. 用NOT/!=/<>:WHERE age != 18(改用WHERE age < 18 OR age > 18,若需全量过滤,建议业务层处理);

  4. 用OR连接非索引字段:WHERE age = 18 OR name = '张三'(name无索引时,索引失效)。

3.2 分页查询优化

3.2.1 避免OFFSET过大
  • 传统分页(LIMIT offset, size)在offset过大时(如LIMIT 10000, 10),会扫描前 10010 条数据,性能差。

  • ✅ 优化方案:用主键分段(适用于主键自增):

-- 第1页:id < 10
SELECT id, user_name FROM user_info WHERE is_deleted = 0 ORDER BY id LIMIT 10;
-- 第2页:id > 10 AND id < 20(以上一页最大id为条件)
SELECT id, user_name FROM user_info WHERE is_deleted = 0 AND id > 10 ORDER BY id LIMIT 10;
3.2.2 覆盖索引分页
  • 若查询字段均在索引中(覆盖索引),无需回表查询,性能大幅提升。
SELECT id, is_deleted FROM user_info WHERE is_deleted = 0 ORDER BY id LIMIT 10000, 10;
  • 示例:idx_is_deleted_id (is_deleted, id)为覆盖索引,查询id和is_deleted时直接用索引:

3.3 其他性能优化点

  1. 禁止使用SELECT DISTINCT(去重会增加排序开销,优先在业务层去重);

  2. 避免ORDER BY非索引字段(大表会导致全表排序,耗时久);

  3. 大表查询用EXPLAIN分析执行计划(查看是否命中索引、是否全表扫描)。

  • 示例:EXPLAIN SELECT * FROM user_info WHERE age > 18;(分析该查询是否命中age索引)。

更多推荐