Skip to content

SQL 规范 ​

TIP

规定 SQL 编写规范,适用于 MySQL 8.4 数据库,包含动态 SQL(MyBatis SQL Provider)编写规范。

设计原则 ​

原则说明
软删除所有查询必须包含 del_flag = 0 条件
防注入动态参数使用 #{} 占位符,禁止字符串拼接
索引友好避免在索引列上使用函数或运算
精简查询只查询需要的字段,禁止 SELECT *
批量操作大数据量操作分批执行,避免长事务

通用规范 ​

大小写 ​

  • SQL 关键字大写:SELECT、FROM、WHERE、AND、OR
  • 表名、字段名小写:sys_user、user_name
  • 别名使用驼峰:user_name userName
sql
-- ✅
SELECT u.id, u.account, u.user_name userName
FROM sys_user u
WHERE u.del_flag = 0 AND u.status = 0;

-- ❌
select u.id,u.account,u.user_name username
from sys_user u
where u.del_flag=0 and u.status=0;

缩进与换行 ​

  • 每个子句(SELECT、FROM、WHERE)独占一行
  • 字段列表每行一个字段
  • 条件较多时每个 AND/OR 换行
sql
-- ✅
SELECT
    u.id,
    u.account,
    u.user_name userName,
    u.mobile,
    u.email
FROM
    sys_user u
    LEFT JOIN sys_dept d ON d.id = u.dept_id
WHERE
    u.del_flag = 0
    AND u.status = 0
    AND u.dept_id = 1001
ORDER BY
    u.create_time DESC;

查询规范 ​

禁止 SELECT * ​

sql
-- ❌ 查询所有字段
SELECT * FROM sys_user WHERE del_flag = 0;

-- ✅ 只查询需要的字段
SELECT id, account, user_name, mobile FROM sys_user WHERE del_flag = 0;

必须包含软删除条件 ​

sql
-- ❌ 缺少 del_flag 条件
SELECT id, account FROM sys_user WHERE status = 0;

-- ✅ 包含 del_flag 条件
SELECT id, account FROM sys_user WHERE del_flag = 0 AND status = 0;

JOIN 规范 ​

  • 明确指定 JOIN 类型(INNER JOIN / LEFT JOIN)
  • 每个 JOIN 必须有 ON 条件
  • 多表 JOIN 时使用表别名
sql
-- ✅
SELECT
    u.id,
    u.account,
    u.user_name userName,
    d.dname deptName
FROM
    sys_user u
    LEFT JOIN sys_dept d ON d.id = u.dept_id AND d.del_flag = 0
WHERE
    u.del_flag = 0;

-- ❌ 缺少 ON 条件
SELECT u.id, d.dname
FROM sys_user u, sys_dept d
WHERE u.dept_id = d.id;

分页查询 ​

sql
-- ✅ 使用 MyBatis-Plus Page 分页
@SelectProvider(SysUserSqlProvider.class)
IPage<Map<String, Object>> findUsers(Page<Map<String, Object>> page, String conditions);

-- ✅ SQL 中不需要 LIMIT,由 MyBatis-Plus 自动拼接
public static String findUsers(Page<Map<String, Object>> page, String conditions) {
    StringBuffer sql = new StringBuffer();
    sql.append("SELECT u.id, u.account FROM sys_user u ");
    sql.append("WHERE ").append(conditions);
    sql.append(" AND u.del_flag = 0 ");
    sql.append("ORDER BY u.create_time DESC");
    return sql.toString();
}

IN 条件规范 ​

sql
-- ✅ 使用集合参数
SELECT id, account FROM sys_user WHERE id IN (1, 2, 3, 4, 5);

-- ❌ 禁止超长 IN 列表(超过 100 个应分批)
SELECT id, account FROM sys_user WHERE id IN (/* 1000 个 ID */);

写入规范 ​

INSERT ​

sql
-- ✅ 明确指定字段
INSERT INTO sys_user (account, user_name, password, salt, create_by, create_time)
VALUES ('admin', '管理员', 'xxx', 'xxx', 'system', NOW());

-- ❌ 不指定字段
INSERT INTO sys_user VALUES (null, 'admin', ...);

UPDATE ​

sql
-- ✅ 带乐观锁更新
UPDATE sys_user
SET user_name = '新名称',
    last_update_by = 'admin',
    last_update_time = NOW(),
    version = version + 1
WHERE id = 1001
  AND version = 5
  AND del_flag = 0;

-- ❌ 无条件更新(全表更新风险)
UPDATE sys_user SET status = 1;

DELETE(软删除) ​

sql
-- ✅ 软删除(更新 del_flag)
UPDATE sys_user
SET del_flag = 1,
    last_update_by = 'admin',
    last_update_time = NOW()
WHERE id = 1001 AND del_flag = 0;

-- ❌ 物理删除(禁止)
DELETE FROM sys_user WHERE id = 1001;

动态 SQL Provider 规范 ​

Provider 类结构 ​

java
@Slf4j
public class SysUserSqlProvider implements ProviderMethodResolver {

    /**
     * 查询用户列表
     */
    public static String findUsers(Page<Map<String, Object>> page, String conditions) {
        StringBuffer sql = new StringBuffer();
        sql.append(" SELECT ");
        sql.append(" 	u.id, ");
        sql.append(" 	u.account, ");
        sql.append(" 	u.user_name userName, ");
        sql.append(" FROM ");
        sql.append(" 	sys_user u ");
        sql.append(" 	LEFT JOIN sys_dept d ON d.id = u.dept_id ");
        sql.append(" WHERE ");
        sql.append(conditions);
        sql.append(" AND u.del_flag = 0 ");
        sql.append(" ORDER BY u.create_time DESC ");
        return sql.toString();
    }
}

动态条件拼接 ​

java
-- ✅ 使用 StringBuffer 拼接动态条件
public String findUserByCondition(Map<String, Object> param) {
    StringBuffer sql = new StringBuffer();
    sql.append("SELECT id, account, user_name FROM sys_user ");
    sql.append("WHERE del_flag = 0 ");

    if (param.get("account") != null) {
        sql.append(" AND account = '").append(param.get("account")).append("'");
    }
    if (param.get("deptId") != null) {
        sql.append(" AND dept_id = ").append(param.get("deptId"));
    }

    return sql.toString();
}

防注入 ​

java
-- ❌ 直接拼接用户输入(SQL 注入风险)
String sql = "SELECT * FROM sys_user WHERE account = '" + userInput + "'";

-- ✅ 使用参数化或白名单校验
String sql = "SELECT * FROM sys_user WHERE account = '" + escapeInput(userInput) + "'";

-- ✅ 最佳:使用 MyBatis-Plus 的 QueryWrapper
QueryWrapper<SysUserPO> wrapper = new QueryWrapper<>();
wrapper.eq("account", userInput);  // 自动参数化

索引使用规范 ​

避免索引失效 ​

sql
-- ❌ 在索引列上使用函数
SELECT * FROM sys_user WHERE DATE(create_time) = '2024-01-01';

-- ✅ 改为范围查询
SELECT * FROM sys_user WHERE create_time >= '2024-01-01 00:00:00'
                            AND create_time < '2024-01-02 00:00:00';

-- ❌ 隐式类型转换
SELECT * FROM sys_user WHERE mobile = 1*********0;  -- mobile 是 VARCHAR

-- ✅ 类型匹配
SELECT * FROM sys_user WHERE mobile = '1*********0';

-- ❌ 使用 OR 导致全表扫描
SELECT * FROM sys_user WHERE account = 'admin' OR user_name = '管理员';

-- ✅ 改为 UNION
SELECT * FROM sys_user WHERE account = 'admin'
UNION
SELECT * FROM sys_user WHERE user_name = '管理员';

LIKE 查询 ​

sql
-- ❌ 前置通配符无法使用索引
SELECT * FROM sys_user WHERE user_name LIKE '%admin%';

-- ✅ 后置通配符可以使用索引
SELECT * FROM sys_user WHERE user_name LIKE 'admin%';

事务规范 ​

短事务原则 ​

  • 事务中禁止包含网络调用(Feign、HTTP)、文件操作
  • 事务执行时间不超过 3 秒
  • 批量操作分批提交
java
-- ❌ 事务中包含远程调用
@Transactional
public void saveUser(SysUserPO user) {
    sysUserMapper.insert(user);
    redisUserFeignService.updateUser(user);  // 网络调用在事务中
}

-- ✅ 事务外调用远程服务
public void saveUser(SysUserPO user) {
    saveUserInDB(user);  // 事务方法
    redisUserFeignService.updateUser(user);  // 事务外调用
}

@Transactional(rollbackFor = Exception.class)
public void saveUserInDB(SysUserPO user) {
    sysUserMapper.insert(user);
}

批量操作 ​

java
-- ✅ 分批插入(每批 100 条)
int batchSize = 100;
for (int i = 0; i < list.size(); i += batchSize) {
    List<SysUserPO> batch = list.subList(i, Math.min(i + batchSize, list.size()));
    sysUserMapper.batchInsert(batch);
}

反例与正例 ​

全表扫描 ​

sql
-- ❌ 缺少 del_flag 条件,可能扫描已删除数据
SELECT id, account FROM sys_user WHERE status = 0;

-- ✅ 包含软删除过滤
SELECT id, account FROM sys_user WHERE del_flag = 0 AND status = 0;

子查询性能 ​

sql
-- ❌ 相关子查询(每行执行一次子查询)
SELECT u.*,
       (SELECT d.dname FROM sys_dept d WHERE d.id = u.dept_id) deptName
FROM sys_user u
WHERE u.del_flag = 0;

-- ✅ 改为 JOIN
SELECT u.*, d.dname deptName
FROM sys_user u
LEFT JOIN sys_dept d ON d.id = u.dept_id AND d.del_flag = 0
WHERE u.del_flag = 0;

分页深度翻页 ​

sql
-- ❌ 深度翻页性能差
SELECT id, account FROM sys_user WHERE del_flag = 0 LIMIT 1000000, 20;

-- ✅ 使用游标分页(记录上一页最大 ID)
SELECT id, account FROM sys_user WHERE del_flag = 0 AND id > 1000000 ORDER BY id LIMIT 20;

SQL Checklist ​

提交 SQL 或 Provider 代码前自查:

  • 查询包含 del_flag = 0 条件
  • 禁止 SELECT *,只查询需要的字段
  • 字段别名使用驼峰命名
  • JOIN 有明确的 ON 条件
  • 动态参数防注入处理
  • 避免在索引列上使用函数
  • 批量操作分批执行
  • 事务中无远程调用
  • 深度翻页使用游标分页

相关规范 ​