| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- ---
- description: 数据库开发与性能约束(SQL、分页、索引、事务、批量、慢 SQL、上线门禁)
- globs: "**/*.java,**/*.sql,**/mapper/**/*.xml"
- alwaysApply: false
- ---
- # 数据库开发与性能约束规范
- ## 1. 目的
- 为统一数据库相关开发标准,减少慢 SQL、锁等待、全表扫描、连接池耗尽等线上问题,制定本规范。所有涉及数据库访问的后端代码、SQL、Mapper、定时任务、数据同步任务、报表查询均应遵守本规范。
- ## 2. 适用范围
- 本规范适用于以下场景:
- - 新增、修改数据库表结构;
- - 新增、修改业务 SQL;
- - 新增、修改 MyBatis Mapper、JPA、JdbcTemplate 等数据库访问代码;
- - 数据导入、数据同步、批量更新、统计报表;
- - 定时任务、接口查询、后台管理查询;
- - 上线前 SQL 审查与性能评估。
- ## 3. 基本原则
- - 查询必须有明确边界,禁止无条件查询大表。
- - 接口 SQL 必须优先保证稳定响应时间,不允许把复杂离线统计放到在线接口实时计算。
- - 写入操作必须控制事务范围,禁止长事务。
- - 大数据量处理必须分页、分批、可恢复。
- - 代码中禁止拼接不可信 SQL,必须使用参数绑定。
- - 新增 SQL 必须考虑索引、执行计划和数据量增长。
- ## 4. SQL 编写约束
- ### 4.1 查询字段
- - 禁止在业务代码中使用 `SELECT *`。
- - 必须显式列出需要返回的字段。
- - 列表页、分页接口只返回页面需要展示的字段。
- - 大字段(`TEXT`、`CLOB`、大 JSON、二进制字段)不得出现在列表查询中,确需展示时应单独查询详情。
- 示例:
- ```sql
- -- 不允许
- SELECT * FROM tf_wrs_rainfall_b WHERE station_id = ?;
- -- 推荐
- SELECT id, station_id, rainfall_value, measure_time
- FROM tf_wrs_rainfall_b
- WHERE station_id = ?;
- ```
- ### 4.2 WHERE 条件
- - 大表查询必须带有高选择性条件,如主键、业务唯一键、时间范围、机构范围等。
- - 禁止对索引字段使用函数、表达式或隐式类型转换。
- - 禁止使用无法命中索引的前置模糊查询,如 `LIKE '%keyword'`、`LIKE '%keyword%'`。
- - `IN` 条件数量应控制在 200 个以内,超过时应使用临时表、批量分片或关联查询。
- - 所有逻辑删除表的查询默认必须包含 `is_delete = 0`。
- 示例:
- ```sql
- -- 不允许,字段上使用函数会影响索引
- WHERE DATE(create_time) = '2026-04-30'
- -- 推荐
- WHERE create_time >= '2026-04-30 00:00:00'
- AND create_time < '2026-05-01 00:00:00'
- ```
- ### 4.3 排序
- - 排序字段应尽量命中索引。
- - 大表禁止无条件 `ORDER BY create_time DESC`。
- - 分页查询中的排序字段必须稳定,建议追加主键作为二级排序。
- 示例:
- ```sql
- ORDER BY measure_time DESC, id DESC
- ```
- ### 4.4 聚合统计
- - 在线接口禁止对大表进行无边界 `COUNT`、`SUM`、`GROUP BY`。
- - 报表统计应优先使用汇总表、物化结果或离线任务。
- - `GROUP BY` 字段应结合业务场景建立合适索引。
- - 统计口径必须明确时间范围、机构范围、数据状态范围。
- ## 5. 分页规范
- ### 5.1 普通分页
- - 小数据量或后台管理场景可使用 `LIMIT/OFFSET`。
- - 大偏移量分页禁止直接使用超大 `OFFSET`。
- 示例:
- ```sql
- -- 不推荐,大偏移量性能差
- SELECT id, station_id, measure_time
- FROM tf_wrs_rainfall_b
- ORDER BY id
- LIMIT 20 OFFSET 1000000;
- ```
- ### 5.2 深分页
- 深分页应使用游标分页或基于主键的延续查询。
- ```sql
- SELECT id, station_id, measure_time
- FROM tf_wrs_rainfall_b
- WHERE id > ?
- ORDER BY id
- LIMIT 500;
- ```
- ### 5.3 分页返回
- - 接口分页必须限制 `pageSize` 最大值,建议不超过 500。
- - 导出接口不得复用普通分页接口一次性拉取全部数据。
- - 导出必须采用流式、分批或异步任务方式处理。
- ## 6. 索引使用约束
- ### 6.1 新增索引
- 新增索引前必须说明:
- - 支撑哪个接口或任务;
- - 对应 SQL 是什么;
- - 查询条件、排序字段、返回数据量;
- - 预计表数据量;
- - 是否会影响写入性能。
- ### 6.2 组合索引
- - 等值查询字段放前面;
- - 范围查询字段放后面;
- - 排序字段可结合查询条件放入组合索引;
- - 避免重复索引和左前缀重复索引。
- 示例:
- ```sql
- -- 查询条件
- WHERE station_id = ?
- AND measure_time >= ?
- AND measure_time < ?
- ORDER BY measure_time DESC
- -- 推荐索引
- CREATE INDEX idx_rainfall_station_time
- ON tf_wrs_rainfall_b (station_id, measure_time);
- ```
- ### 6.3 禁止项
- - 禁止为低区分度字段单独建索引,如 `is_delete`、`status`、`gender`。
- - 禁止在长字符串字段上直接建立完整索引。
- - 禁止盲目为每个查询字段都建单列索引。
- - 禁止上线未验证执行计划的新索引。
- ## 7. 事务约束
- - 事务中只放必须保持一致的数据操作。
- - 禁止在事务内调用第三方接口、文件上传、远程服务、消息等待、长时间计算。
- - 批量更新、批量删除必须分批提交。
- - 事务内查询数据量应可控,禁止锁住大范围数据后再逐条处理。
- - 涉及库存、余额、状态流转等并发修改时,必须明确锁策略。
- 示例:
- ```java
- // 不推荐:事务内调用远程接口
- @Transactional
- public void updateAndNotify() {
- repository.updateStatus(id);
- remoteClient.notify(id);
- }
- ```
- ## 8. 批量处理约束
- ### 8.1 批量查询
- - 禁止一次性加载大量数据到内存。
- - 批量任务必须分页读取。
- - 每批数量建议控制在 500 到 5000,具体按数据大小和数据库压力调整。
- ### 8.2 批量写入
- - 使用批量插入或批量更新能力,避免循环单条 SQL。
- - 大批量写入必须分批提交。
- - 批量任务必须具备失败重试、断点续跑或幂等能力。
- ### 8.3 批量删除
- - 生产环境禁止直接大范围物理删除。
- - 逻辑删除也必须分批执行。
- - 历史数据清理必须先评估影响行数,并在低峰期执行。
- ## 9. 代码开发约束
- ### 9.1 Mapper 与 DAO
- - Mapper 方法名应表达查询意图,禁止使用 `queryList1`、`selectData` 等模糊命名。
- - Mapper 入参必须使用明确对象或 `@Param`,避免大量散乱参数。
- - 返回集合的方法必须有明确数据量控制。
- - 查询单条数据时必须使用唯一条件,并处理多条异常情况。
- ### 9.2 动态 SQL
- - 动态 SQL 必须避免条件缺失导致全表扫描。
- - 多条件查询至少应有一个核心过滤条件。
- - 可选条件全部为空时,应直接拒绝查询或返回空结果。
- - 排序字段不得直接接收前端原始字符串,必须使用白名单映射。
- 示例:
- ```java
- private static final Map<String, String> SORT_FIELD_MAP = Map.of(
- "measureTime", "measure_time",
- "createTime", "create_time"
- );
- ```
- ### 9.3 防 SQL 注入
- - 禁止使用字符串拼接构造用户输入条件。
- - MyBatis 中普通参数必须使用 `#{}`。
- - `${}` 仅允许用于经过白名单校验的表名、字段名、排序方向。
- 示例:
- ```xml
- <!-- 不允许 -->
- ORDER BY ${sortField} ${sortOrder}
- <!-- 推荐:代码中先做白名单映射后再传入 -->
- ORDER BY ${safeSortField} ${safeSortOrder}
- ```
- ## 10. 连接池与资源释放
- - 禁止手工创建数据库连接,统一使用项目连接池。
- - ResultSet、Statement、Connection 如手工使用必须正确关闭。
- - 慢任务不得长时间占用数据库连接。
- - 定时任务并发数必须受控,避免同时打满连接池。
- - 数据库连接池大小调整必须结合接口并发、SQL耗时和数据库承载能力评估。
- ## 11. 定时任务与报表约束
- - 定时任务必须限制单次处理数据量。
- - 统计报表应优先写入中间表或结果表,接口直接读取结果。
- - 定时任务必须避免与业务高峰写入时间冲突。
- - 任务必须记录开始时间、结束时间、处理数量、失败数量、耗时。
- - 任务重复执行必须幂等。
- ## 12. 慢 SQL 标准
- 满足以下任一条件,应按慢 SQL 处理:
- - 在线接口单条 SQL 超过 500ms;
- - 后台管理查询单条 SQL 超过 1s;
- - 报表统计单条 SQL 超过 3s;
- - SQL 扫描行数远大于返回行数;
- - SQL 执行计划出现大表全表扫描;
- - SQL 导致明显锁等待或连接池排队。
- 慢 SQL 必须记录:
- - SQL 原文;
- - 参数;
- - 表数据量;
- - 执行计划;
- - 扫描行数和返回行数;
- - 优化前后耗时对比。
- ## 13. 上线前检查清单
- 新增或修改数据库访问代码上线前,必须检查:
- - [ ] 是否禁止 `SELECT *`;
- - [ ] 是否具备必要 WHERE 条件;
- - [ ] 是否默认过滤 `is_delete = 0`;
- - [ ] 是否存在深分页问题;
- - [ ] 是否存在大事务或事务内远程调用;
- - [ ] 是否存在循环单条 SQL;
- - [ ] 是否存在无白名单的动态排序;
- - [ ] 是否验证执行计划;
- - [ ] 是否评估索引影响;
- - [ ] 是否提供大数据量处理方案;
- - [ ] 是否对导出、报表、定时任务做限流或分批;
- - [ ] 是否在预发布环境使用接近真实数据量验证。
- ## 14. 禁止上线项
- 以下问题未处理前禁止上线:
- - 无条件查询大表;
- - 大表无索引分页或排序;
- - 接口中实时执行大范围统计;
- - 事务内调用外部服务;
- - 循环逐条查询或逐条更新形成 N+1 SQL;
- - 前端参数直接拼接 SQL;
- - 批量任务无分页、无幂等、无失败恢复;
- - 删除或更新语句缺少明确范围条件;
- - 未经评估直接修改大表字段类型;
- - 高风险 DDL 无回滚或备份方案。
- ## 15. 评审要求
- 涉及数据库的代码评审必须关注:
- - SQL 是否可命中索引;
- - 数据量增长后是否仍可接受;
- - 事务范围是否合理;
- - 是否存在并发写入风险;
- - 是否存在 N+1 查询;
- - 是否存在不安全动态 SQL;
- - 是否有必要的日志和监控指标;
- - 是否符合上线前检查清单。
|