SQL 版本化规范
研发开发规范
- 按规范命名 SQL 脚本(<序号>_<语义>.sql)并放进镜像分支树的对应目录
- 写出「跑多少遍结果都一样」的幂等脚本
- 知道改完数据库结构后,要登记到 CHANGELOG 并走现场流程
- 幂等
- 执行多次和执行一次效果相同。比如
INSERT前先检查是否已存在,就不会重复插入。这是增量脚本的核心要求。 - 增量 vs 全量
- 全量建库是从零建整个库;增量脚本只做「加法」(新增列、新增表),不破坏已有数据。
- CHANGELOG
- 变更日志,记录每个脚本的执行情况。现场部署时按顺序执行脚本,并在 CHANGELOG 中登记,保证可追溯。
在仓库根目录建 sql/,目录树镜像分支树:分支叫什么路径,SQL 就放同路径目录。SQL 在 feature 分支目录中生长(hotfix 分支禁改库,见 03·规则三),最终汇总进 sql/release/<版本>/;打包发版当天冻结为 sql/tag/<版本>/ 最终版(以下为 EasyPro 实际结构,其他产品同理):
两个「不产出」:hotfix 分支不产出 SQL(03·规则三禁改库);定制 SQL 一律放 doc/定制/<客户>/(03·定制 SQL 隔离),禁止混入通用 sql/:通用目录按版本顺序执行到所有现场,混放会导致其他现场误执行定制脚本。
目录即归属:看路径就知道脚本属于哪条分支、处于哪个阶段;托管平台上与分支列表一致,按前缀自动折叠聚拢。
| 分支 / 节点 | SQL 目录 | 定位 |
|---|---|---|
release/<版本> | sql/release/<版本>/ | 该版本交付 SQL 的汇总终点:feature 的 SQL 最终都汇入这里 |
feature/<版本>/<语义> | sql/feature/<版本>/<语义>/ | 开发期草稿,合入 release 时按主题汇总进对应序号文件 |
hotfix/<版本>/<语义> | 不产出 | hotfix 禁改库(03·规则三);已发布版本需改库走 custom,SQL 放 doc/定制/ |
custom/<版本>/<语义> | 不入 sql/ | 定制 SQL 一律放 doc/定制/(03·定制 SQL 隔离),禁止混入通用 |
dev/<人名> | sql/dev/<人名>/(可选) | 个人试验脚本;不合入则随分支一并废弃 |
| 产品线四段式 | 前缀后插产品代号段 | 如 sql/feature/eos/1.1.0/<语义>/、sql/line/eos/1.0.0/(子产品用自身版本号) |
tag vX.Y.Z(打包) | sql/tag/<版本>/ | 打包最终版:打包当天由 release 目录冻结而来,安装包附带 |
生命周期规则:
- 创建:规划命名的分支创建时即建同名占位目录;暂无脚本时放一行
README.md占位锚定目录,写明对应分支与汇总去向。 - 汇总:feature 的 SQL 合入 release 时按主题归入对应序号文件(同主题追加到已有序号文件末尾,新主题新建下一序号文件);hotfix 分支不产出 SQL。
- 序号冲突:并行分支出现同序号不同主题时,后合入者改取下一可用序号。
- 发版打包:打 tag 当天,把
sql/release/<版本>/全量复制为sql/tag/<版本>/(检查项:该版本的 feature 目录应已全部合入清空)。此后 tag 目录冻结——安装包附带 tag 目录 SQL,现场按序号顺序执行。 - 发版后:已打包版本的 tag 目录不回改;修正请改
sql/release/<版本>/并走下个版本 / hotfix(不改库)/ 维护性微版本流程,通用 DB 修复经回流进入下个开发中版本的汇总。 - 删除:分支合并删除时,其 SQL 已汇总/回流,对应分支目录一并清理,不在
sql/留空壳。 - 基线:2.1.0 为首个规范化交付版本,目录内含全量建库(
ibms-*.sql)+ 增量升级;新现场从 2.1.0 全量起步,老现场按版本逐级升级。
文档侧有一套同构规范:目录同样镜像分支树、生命周期与分支联动、doc/README.md 为唯一入口——但合入策略相反(文档原地保留、不汇总)。见 10·文档目录规范。
2.1.1 起统一按主题分文件、数字序号定执行顺序:
<序号>_<语义>.sql
- 序号从 0 起,在同一
sql/release/<版本>/内递增,数字即执行顺序(CHANGELOG 登记顺序须与之一致)。 - 语义用英文小写 + 下划线,禁止中文、空格;一个驱动/模块一个序号文件(如
0_common.sql、3_modbus.sql)。 - 同一主题的后续变更追加到已有序号文件末尾,不新开文件;新主题才取下一序号。
- 2.1.0 目录的
ibms-*.sql(全量建库)、upgrade_2.1.0*.sql、example.sql为历史命名,沿用不追溯。
示例:
0_common.sql、6_knx.sqlV2.1.1.1__modbus_write_function_code.sql(V 前缀旧式已弃用,目录名已含版本号)2.1.1增量.sql(中文、无序号)update.sql(无序号、无语义)
核心要求:增量修改,绝不破坏原有数据。
| 允许 | 禁止 |
|---|---|
ALTER TABLE ... ADD COLUMN | DROP TABLE |
ALTER TABLE ... ADD INDEX | DROP COLUMN(确需时走安全流程) |
INSERT(带幂等保护) | TRUNCATE TABLE |
UPDATE(迁移数据) | DELETE 全表 |
CREATE TABLE IF NOT EXISTS | 直接 DROP 后重建 |
幂等性要求:每个增量脚本必须可重复执行而不报错、不重复插入。标准做法:
- 列存在性判断:
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE ...) - 数据存在性判断:游标遍历 +
IF NOT EXISTS插入 - 沿用已验证可靠的「游标遍历 + 幂等保护」成熟范式(现汇总于
sql/release/2.1.1/3_modbus.sql,可直接参考)。
幂等脚本(跑 N 遍都安全)
-- 加列前先判断是否已存在
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name='iot_driver'
AND column_name='protocol_type'
) THEN
ALTER TABLE iot_driver
ADD COLUMN protocol_type VARCHAR(32);
END IF;非幂等脚本(跑第二遍就崩)
-- 直接加,不管列在不在 ALTER TABLE iot_driver ADD COLUMN protocol_type VARCHAR(32); -- 第二遍执行报错: -- Duplicate column name 'protocol_type'
破坏性变更的安全流程(确需删列/改类型):
- 先在新列写入数据(兼容期)。
- 发版切换读写。
- 下一版本再删旧列(此时数据已迁移)。
- 删列脚本必须附
doc/规则/备案与回滚脚本。
为什么破坏性变更要分好几步,不能一把梭?因为现场不是所有人同时升级。A 客户升到新版、B 客户还在跑旧版——如果你一个脚本里既加新列又删旧列,B 客户的旧代码还在 SELECT 旧列,当场报错崩溃。分步走(先加、再切、后删)保证新旧版本都能正常运行,给现场留出升级窗口期。这是「向前兼容」原则。
-- =============================================================
-- <版本号> <功能简述>
-- 功能:<详细说明,多个要点用 1.2.3.>
-- 影响库表:<库名>.<表名>
-- 前置条件:<依赖的前置脚本/数据状态>
-- 幂等说明:<如何保证重复执行不报错>
-- 作者:<姓名> 日期:YYYY-MM-DD 关联版本:<release/x.x.x>
-- =============================================================
USE `ibms-iot`;
-- 步骤 1:结构变更(带幂等保护)
ALTER TABLE `iot_driver_attribute`
MODIFY COLUMN `id` bigint(20) NOT NULL AUTO_INCREMENT FIRST;
-- 步骤 2:数据变更(游标遍历 + 幂等保护)
DELIMITER $$
DROP PROCEDURE IF EXISTS proc_upgrade_xxx$$
CREATE PROCEDURE proc_upgrade_xxx()
BEGIN
-- 游标定义、遍历、IF NOT EXISTS 插入 ...
END$$
DELIMITER ;
CALL proc_upgrade_xxx();
DROP PROCEDURE IF EXISTS proc_upgrade_xxx;该模板源自已验证可靠的「游标遍历 + 幂等保护」范式(现汇总于 sql/release/2.1.1/3_modbus.sql)。实际落库示例可参考 sql/release/2.1.1/7_rcp.sql 的头部注释——来源分支、汇总去向、作用与幂等说明一应俱全:
-- 来源:doc/驱动设计文档/RCP驱动/工程同步机制/RCP驱动_属性升级SQL.sql -- (feature/2.1.1/Rcp同步工程文件机制 合入 release/2.1.1 时按规范汇总归档,序号 7) -- 作用:rcp 驱动新增 6 个属性模板……幂等可重复执行
sql/CHANGELOG.md 作为版本登记表,记录每个脚本的执行情况(唯一权威:顺序、现场登记):
| 字段 | 说明 |
|---|---|
| 版本 | 对应 release/x.x.x |
| 脚本 | 文件名(含 sql/ 下目录路径,如 release/2.1.1/3_modbus.sql;打包后在 sql/tag/<版本>/ 存同名冻结副本) |
| 类型 | 结构变更 / 数据变更 / 兼容 |
| 影响表 | <库>.<表> |
| 作者 | 姓名或工号 |
| 日期 | YYYY-MM-DD |
| 现场执行 | 现场名 + 执行日期(多现场分行登记) |
现场执行流程:
- 现场拿到新版本安装包后,按
sql/tag/<目标版本>/目录下的脚本顺序执行;新现场首次部署先执行sql/release/2.1.0/全量建库;老现场跨版本升级,从自己所处版本之后的 tag 目录起逐版本执行到目标版本。 - 每执行完一个脚本,在
CHANGELOG.md对应行的「现场执行」列登记。 - 执行顺序严格遵循 CHANGELOG 的登记顺序(结构变更优先于依赖它的数据变更);历史上手工执行过的脚本重复执行无害(幂等),现场登记以首次执行为准。
我在 Navicat 里直接改了表结构,没写脚本,有问题吗?
<序号>_<语义>.sql,放进对应分支的镜像目录),提交到仓库,这样所有人 pull 后能同步、现场升级时能执行。直接在工具里改 = 改动丢失,是 SQL 规范的头号禁忌。我的脚本写成了 ALTER TABLE ADD COLUMN,现场跑了两次,多加了一列?
IF NOT EXISTS (SELECT ... FROM syscolumns ...) 再 ADD。幂等原则保证脚本重复执行也安全,因为现场运维人员可能误操作、脚本可能因中断被重跑。我 DROP 了一个没用的旧列,清理一下,这样对吧?
我加了脚本但忘了登记 CHANGELOG,会怎样?
CHANGELOG.md,写清版本、描述、执行顺序,这是现场升级流程的依据。之前不是 V 开头命名吗(V2.1.1.1__xxx.sql),为什么现在不让用了?
sql/release/2.1.1/)表达,文件名再带一遍属于冗余。2.1.1 起统一为 <序号>_<语义>.sql:序号即执行顺序,语义即主题,一眼可读。旧文件(2.1.0 目录的 ibms-*.sql、upgrade_2.1.0*.sql)沿用不追溯。我在 hotfix 分支上顺手加了一列,改完才发现规范说 hotfix 禁改库,怎么办?
custom/ 定制分支,SQL 放 doc/定制/<客户>/;所有现场都需要的通用变更 → 进下个开发中版本的 sql/release/<版本>/ 对应序号文件;多个现场紧急需要 → 走维护性微版本(03·规则五)。hotfix 本身只改代码,保持「现场快速修复、不跑升级脚本」的节奏。