SQL代码审查是保障数据库安全、性能和可维护性的关键环节。与应用程序代码审查不同,SQL审查需要关注安全漏洞(如SQL注入)、查询性能、数据一致性、命名规范等多个维度。本文提供一套完整的SQL代码审查清单,帮助团队建立规范的审查流程。
一、安全性审查
1.1 SQL注入防护
SQL注入是最常见的数据库安全漏洞。在代码审查中,需要重点检查以下内容:
危险模式:字符串拼接SQL
-- 危险:直接拼接用户输入
SELECT * FROM users WHERE username = ' + @username + ' AND password = ' + @password;
-- 危险:动态表名拼接
EXEC('SELECT * FROM ' + @tableName + ' WHERE id = ' + @id);
-- 危险:LIKE拼接
SELECT * FROM products WHERE name LIKE '%" + keyword + "%';
安全模式:参数化查询
-- 安全:使用参数化查询
SELECT * FROM users WHERE username = @username AND password = @password;
-- 安全:使用预处理语句
PREPARE stmt FROM 'SELECT * FROM users WHERE username = ? AND password = ?';
EXECUTE stmt USING @username, @password;
1.2 权限检查清单
| 检查项 | 风险级别 | 说明 |
|---|---|---|
| 应用程序使用DBA账号 | 极高 | 应使用最小权限账号 |
| 存储过程中使用动态SQL | 高 | 需检查输入参数验证 |
| 直接暴露表名给前端 | 高 | 应通过API层抽象 |
| 未加密的敏感数据 | 高 | 密码、手机号等需加密 |
| 过度授权的数据库用户 | 中高 | 只授予必要的权限 |
| 缺少审计日志 | 中 | 关键操作需要审计追踪 |
1.3 敏感数据处理
-- 审查要点:敏感字段是否加密存储
-- 不安全
CREATE TABLE users (
user_id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(100), -- 明文密码,极度危险
phone VARCHAR(20), -- 明文手机号
id_card VARCHAR(30) -- 明文身份证号
);
-- 安全做法
CREATE TABLE users (
user_id INT PRIMARY KEY,
username VARCHAR(50),
password_hash VARCHAR(255), -- 使用bcrypt等哈希算法
phone_encrypted VARBINARY(255), -- 加密存储
id_card_encrypted VARBINARY(255), -- 加密存储
phone_masked VARCHAR(20), -- 用于显示的脱敏值(如138****8000)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
二、性能审查
2.1 查询性能检查
-- 审查要点1:是否使用了SELECT *
-- 不通过
SELECT * FROM orders WHERE user_id = ?;
-- 通过:指定需要的列
SELECT order_id, order_date, total_amount, status
FROM orders
WHERE user_id = ?;
-- 审查要点2:是否缺少必要的WHERE条件
-- 不通过:全表扫描
SELECT * FROM products ORDER BY created_at DESC;
-- 通过:添加分页限制
SELECT product_id, name, price, created_at
FROM products
ORDER BY created_at DESC
LIMIT 20 OFFSET 0;
2.2 JOIN性能检查
-- 审查要点:JOIN条件是否有索引支持
-- 检查每个JOIN的关联列是否建立了索引
-- 检查方式
EXPLAIN SELECT
o.order_id,
u.username,
p.product_name
FROM orders o
INNER JOIN users u ON o.user_id = u.user_id
INNER JOIN order_items oi ON o.order_id = oi.order_id
INNER JOIN products p ON oi.product_id = p.product_id
WHERE o.order_date >= '2024-01-01';
-- 审查清单
-- [ ] 每个JOIN条件列是否有索引
-- [ ] JOIN表的数量是否合理(建议不超过5个)
-- [ ] 是否可以用子查询减少JOIN数量
-- [ ] 大表JOIN是否使用了合适的驱动表
2.3 索引使用检查
| 检查项 | 通过标准 | 常见问题 |
|---|---|---|
| WHERE条件列 | 已建立索引 | 频繁查询的列无索引 |
| JOIN关联列 | 已建立索引 | 外键列缺少索引 |
| ORDER BY列 | 索引顺序匹配 | 排序列与索引顺序不一致 |
| 复合索引 | 遵循最左前缀 | 索引列顺序不合理 |
| 重复索引 | 不存在 | 存在功能重复的索引 |
| 未使用索引 | 定期清理 | 大量索引从未被使用 |
三、命名规范审查
3.1 数据库对象命名
-- 审查标准:命名规范一致性
-- 表名规范
-- 推荐:snake_case,使用复数形式
CREATE TABLE user_orders (...);
CREATE TABLE product_categories (...);
-- 不推荐:混合大小写或驼峰命名
CREATE TABLE UserOrders (...);
CREATE TABLE productCategories (...);
-- 字段名规范
-- 推荐:snake_case,语义明确
user_id -- 用户ID
created_at -- 创建时间
updated_at -- 更新时间
is_active -- 是否激活(布尔型使用is_前缀)
order_count -- 订单数量(计数使用_count后缀)
-- 不推荐
userId -- 驼峰命名
Created_At -- 不一致的大小写
flag -- 含义不明确
data -- 过于宽泛
3.2 命名规范检查表
| 对象类型 | 规范 | 正确示例 | 错误示例 |
|---|---|---|---|
| 表名 | snake_case,复数 | user_orders | UserOrders |
| 字段名 | snake_case | created_at | CreatedAt |
| 索引名 | idx_表名_列名 | idx_orders_user_id | index1 |
| 外键名 | fk_表名_关联表 | fk_orders_users | constraint1 |
| 存储过程 | sp_前缀_操作 | sp_create_order | proc1 |
| 触发器 | tr_表名_操作 | tr_users_audit | trigger1 |
| 视图名 | v_前缀_描述 | v_active_users | view1 |
四、数据结构审查
4.1 数据类型选择
-- 审查要点:数据类型是否合理
-- 不推荐的数据类型选择
CREATE TABLE products (
id VARCHAR(20), -- 主键不应用VARCHAR(除非UUID)
name TEXT, -- 短文本不应用TEXT
price FLOAT, -- 金额不应用FLOAT(精度问题)
description VARCHAR(50), -- 长度过短
stock INT UNSIGNED, -- 可能为负数(退货场景)
created_at VARCHAR(30) -- 日期不应用VARCHAR
);
-- 推荐的数据类型
CREATE TABLE products (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200), -- 预估最大长度
price DECIMAL(10, 2), -- 金额使用DECIMAL
description TEXT, -- 长文本使用TEXT
stock INT DEFAULT 0, -- 允许负数或添加CHECK约束
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_name (name)
);
4.2 约束检查
| 约束类型 | 检查内容 | 示例 |
|---|---|---|
| 主键 | 每个表必须有主键 | id INT PRIMARY KEY |
| 非空约束 | 必填字段是否设置NOT NULL | email VARCHAR(100) NOT NULL |
| 默认值 | 是否需要设置默认值 | status VARCHAR(20) DEFAULT 'active' |
| 外键约束 | 关联关系是否有外键约束 | FOREIGN KEY (user_id) REFERENCES users(id) |
| 检查约束 | 数据范围是否有限制 | CHECK (price > 0) |
| 唯一约束 | 业务唯一性是否保证 | UNIQUE (email) |
五、逻辑正确性审查
5.1 边界条件检查
-- 审查要点:是否考虑了边界情况
-- 需要审查的边界条件
-- 1. NULL值处理
SELECT
COALESCE(phone, '未填写') AS phone,
IFNULL(email, '无邮箱') AS email
FROM users;
-- 2. 除零保护
SELECT
revenue,
cost,
CASE
WHEN cost = 0 THEN NULL
ELSE revenue / cost
END AS profit_margin
FROM financial_data;
-- 3. 空结果集处理
SELECT
u.username,
COALESCE(SUM(o.total_amount), 0) AS total_spent
FROM users u
LEFT JOIN orders o ON u.user_id = o.user_id
GROUP BY u.username;
-- 使用LEFT JOIN确保没有订单的用户也被返回
5.2 事务完整性
-- 审查要点:多步操作是否在事务中
-- 不通过:缺少事务保护
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
-- 如果这里出错,下面的操作不会执行,但上面的已扣款
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
-- 通过:使用事务
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
INSERT INTO transfer_log (from_account, to_account, amount, transfer_date)
VALUES (1, 2, 100, NOW());
COMMIT;
-- 任何一步失败都可以ROLLBACK
5.3 并发安全
-- 审查要点:是否存在竞态条件
-- 危险:没有锁保护的库存扣减
SELECT stock FROM products WHERE product_id = 1;
-- 应用程序判断 stock > 0 后...
UPDATE products SET stock = stock - 1 WHERE product_id = 1;
-- 并发场景下可能导致超卖
-- 安全:使用原子操作
UPDATE products
SET stock = stock - 1
WHERE product_id = 1
AND stock > 0;
-- 或使用乐观锁
UPDATE products
SET stock = stock - 1,
version = version + 1
WHERE product_id = 1
AND version = @current_version
AND stock > 0;
六、可维护性审查
6.1 代码结构检查
-- 审查要点:代码结构是否清晰
-- 不通过:代码混杂,难以理解
SELECT a.id,b.name,SUM(c.amount) FROM t1 a,t2 b,t3 c WHERE a.id=b.aid AND b.id=c.bid AND a.status=1 GROUP BY a.id,b.name;
-- 通过:结构清晰,意图明确
SELECT
u.user_id,
u.username,
SUM(p.payment_amount) AS total_payments
FROM users u
INNER JOIN accounts a
ON u.user_id = a.user_id
INNER JOIN payments p
ON a.account_id = p.account_id
WHERE u.status = 'active'
AND p.payment_date >= '2024-01-01'
GROUP BY
u.user_id,
u.username
ORDER BY total_payments DESC;
6.2 代码审查清单总结
| 审查维度 | 权重 | 关键检查项 |
|---|---|---|
| 安全性 | 30% | SQL注入、权限、敏感数据 |
| 性能 | 25% | 索引使用、JOIN优化、分页 |
| 正确性 | 20% | 边界条件、事务、并发安全 |
| 规范性 | 15% | 命名规范、格式、注释 |
| 可维护性 | 10% | 代码结构、复用性、文档 |
七、自动化审查工具
7.1 推荐工具
| 工具 | 类型 | 支持数据库 | 主要功能 |
|---|---|---|---|
| SQLFluff | CLI/CI | 多数据库 | 格式化和规则检查 |
| SonarQube | 平台 | 多数据库 | 综合代码质量分析 |
| pgBadger | 分析工具 | PostgreSQL | 性能分析报告 |
| pt-query-digest | CLI | MySQL | 慢查询分析 |
| EXPLAIN可视化 | 在线工具 | 多数据库 | 执行计划图形化 |
7.2 CI/CD集成示例
将SQL代码审查集成到CI/CD流程中:
# GitHub Actions 示例
name: SQL Review
on: [pull_request]
jobs:
sql-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: SQLFluff Lint
run: |
pip install sqlfluff
sqlfluff lint --dialect mysql sql/
- name: Check for SELECT *
run: |
if grep -rn "SELECT \*" sql/; then
echo "发现 SELECT *,请指定具体列"
exit 1
fi
八、总结
SQL代码审查是保障数据库质量的重要环节。通过建立系统化的审查清单,团队可以:
- 预防安全漏洞:重点关注SQL注入和权限管理
- 保障查询性能:检查索引使用和查询结构
- 确保数据正确:验证边界条件和事务完整性
- 统一代码风格:维护一致的命名和格式规范
- 提升可维护性:保持代码结构清晰、注释完善
建议团队将本审查清单作为代码审查的标准流程,并在CI/CD中集成自动化检查工具,从源头把控SQL代码质量。