SQL代码审查要点

工具相关 ·

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_ordersUserOrders
字段名snake_casecreated_atCreatedAt
索引名idx_表名_列名idx_orders_user_idindex1
外键名fk_表名_关联表fk_orders_usersconstraint1
存储过程sp_前缀_操作sp_create_orderproc1
触发器tr_表名_操作tr_users_audittrigger1
视图名v_前缀_描述v_active_usersview1

四、数据结构审查

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 NULLemail 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 推荐工具

工具类型支持数据库主要功能
SQLFluffCLI/CI多数据库格式化和规则检查
SonarQube平台多数据库综合代码质量分析
pgBadger分析工具PostgreSQL性能分析报告
pt-query-digestCLIMySQL慢查询分析
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代码审查是保障数据库质量的重要环节。通过建立系统化的审查清单,团队可以:

  1. 预防安全漏洞:重点关注SQL注入和权限管理
  2. 保障查询性能:检查索引使用和查询结构
  3. 确保数据正确:验证边界条件和事务完整性
  4. 统一代码风格:维护一致的命名和格式规范
  5. 提升可维护性:保持代码结构清晰、注释完善

建议团队将本审查清单作为代码审查的标准流程,并在CI/CD中集成自动化检查工具,从源头把控SQL代码质量。

阅读 13