浏览知识库目录

MySQL

数据库、表、约束与 DDL 演进

使用主键、唯一键、外键、CHECK 与有序迁移创建可演进的 InnoDB Schema,并评估 DDL 锁与回滚,并在可重复的 StudyStore 实验中验证结果、失败边界与恢复方式。

数据库、表、约束与 DDL 演进

DDL 是数据契约的版本历史。一次 CREATE TABLE 成功不代表设计可长期演进;还要考虑约束名称、迁移顺序、已有脏数据、在线算法、元数据锁和失败后的恢复。迁移文件一旦发布就不应被偷偷重写。


一、学习目标

  • 创建命名清晰的表、键与约束
  • 理解外键动作和 CHECK 的业务含义
  • 按不可变迁移顺序演进 Schema
  • 评估 ALTER TABLE 算法、锁与大表风险
  • 设计前向修复与恢复策略

二、基础 DDL

订单表明确引擎、字符集和约束:

CREATE TABLE orders (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  customer_id BIGINT UNSIGNED NOT NULL,
  status VARCHAR(20) NOT NULL,
  created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  PRIMARY KEY (id),
  CONSTRAINT fk_orders_customer
    FOREIGN KEY (customer_id) REFERENCES customers(id),
  CONSTRAINT chk_orders_status
    CHECK (status IN ('pending','paid','cancelled'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

显式命名约束便于诊断和后续修改。表名、列名与约束命名采用一致规则,不使用保留字或依赖大小写文件系统差异。


三、外键动作

ON DELETE CASCADE 会把父删除传播到子表,适合真正从属且删除语义一致的对象;订单与客户通常不应因客户删除而消失,可选择限制删除或软删除客户。

外键两端类型、符号和索引要兼容。外键保护同一实例内引用完整性,不跨服务或分片。批量导入时关闭外键检查会跳过验证,重新打开并不会自动检查期间写入的所有数据,因此不能当常规迁移捷径。


四、不可变迁移

迁移按编号保存:

001_create_core.sql
002_add_inventory.sql
003_add_order_indexes.sql

已在任何共享环境执行的文件保持不可变,新变化创建新迁移。维护 schema_migrations 记录版本和校验和。迁移脚本检查预期前置版本,失败立即停止。

“可重复执行”不是要求每条 DDL 都加 IF NOT EXISTS;它意味着在同一已知起点产生确定结果。无条件忽略已存在对象可能掩盖定义不一致。


五、ALTER、锁与算法

ALTER TABLE 的成本取决于具体操作、表定义和版本。使用 EXPLAIN ALTER TABLE 或官方支持矩阵确认算法,并显式关注 ALGORITHMLOCK 与元数据锁。

即使是 instant 操作,也可能等待持有元数据锁的长事务。执行前检查活动事务、复制延迟、磁盘空间和回滚方案;大表变更应在与生产数据量相近的环境计时。不要只在十行测试表上判断“在线”。


六、回滚与前向修复

DDL 与事务回滚语义不同于普通 DML,许多语句隐式提交。删除列或收窄类型可能不可逆,先分阶段:

  1. 新增兼容列或表。
  2. 双写或回填并校验。
  3. 切换读取。
  4. 观察后停止旧写入。
  5. 最后删除旧结构。

生产迁移失败通常采用前向修复,而不是假定整份脚本可 ROLLBACK。备份是最后防线,但大规模恢复也需要时间窗口。


七、从知识点到工程契约

本篇 SQL 最终要运行在长期存在的 schema、连接和事务中,而不是只在一个临时查询窗口里得到一次正确结果。先写清输入表与行数、会话设置、事务边界、允许的锁、期望结果和失败后的状态,再决定使用约束、查询、索引、存储对象还是运维命令。数据库行为同时受到版本、隔离级别、字符集、统计信息和并发会话影响,示例必须把这些前提显式化。

可以用以下顺序把知识点落到 StudyStore:

  1. 在全新数据库按迁移顺序建立最小 schema,保存 SHOW CREATE TABLE 与关键会话变量。
  2. 加入一个与“发布后修改旧迁移文件,导致环境历史分叉”相关的反例,记录错误码、SQLSTATE、事务是否仍可用以及数据是否改变。
  3. 为正常、空集、边界值、重复值和并发冲突准备确定性种子,不依赖手工残留数据。
  4. 对读查询检查结果集和 EXPLAIN ANALYZE;对写操作同时检查受影响行数、约束、提交与回滚。
  5. 最后才讨论性能优化。索引和参数调整必须有执行计划、等待事件或容量数据支持,并保留变更前基线。

审查数据库设计时至少回答四个问题:谁能写,哪条约束保护不变量,事务在哪一层结束,失败后如何恢复。MySQL 能保证声明范围内的事务和持久性,却不会替应用补上缺失约束、幂等键、备份演练或最小权限。只要这些问题没有明确答案,就不要把一次成功执行当成生产方案。

本篇最重要的能力是“创建命名清晰的表、键与约束”。能由主键、唯一键、外键、CHECK 或数据类型表达的规则,应优先落到数据库;需要跨聚合或外部系统判断的规则,再由应用和事务协调。不要用注释或约定替代可执行约束。


八、验证策略与复盘

验证分为 schema、数据、并发和恢复四层。schema 层检查定义与会话基线;数据层断言结果和约束;并发层至少用两个独立连接观察锁与隔离;恢复层在新实例重放迁移、备份与恢复。只看客户端显示“Query OK”不能证明结果正确,更不能证明在另一组数据和并发时仍正确。

建议保存下面的实验记录:

项目 需要记录的证据
版本 SELECT VERSION() 与镜像标签
会话 sql_mode、time_zone、事务隔离与字符集
输入 schema 版本、种子行数和参数值
输出 结果集、受影响行数、警告、错误码与 SQLSTATE
性能 执行计划、实际行数、耗时与等待
恢复 COMMIT/ROLLBACK 结果、备份校验和与恢复行数

每组脚本都应能在空数据库从头执行,并通过显式断言退出非零。涉及权限时分别用管理员和最小权限账户连接;涉及锁时设置有限等待,避免测试永久挂起;涉及复制时等待 GTID 状态而不是固定 sleep。清理只删除本次创建的临时容器、网络和数据库。

本篇可以用以下目标做验收:理解外键动作和 CHECK 的业务含义;按不可变迁移顺序演进 Schema;评估 ALTER TABLE 算法、锁与大表风险。把每个目标转换为一条可重复 SQL、一个预期错误或一项恢复检查。若优化后结果正确但计划退化,应把执行计划也纳入回归证据。

发布前再从三个方向反向审查:把数据量放大两个数量级,判断扫描、锁和日志是否仍可控;让两个会话以最不利顺序并发,判断约束和事务是否仍保护不变量;让执行在任意一步失败,判断备份、回滚或幂等键能否恢复。只在十几行数据和单连接中成功的 SQL 仍只是功能草稿。

最后把脚本交给一个只知道镜像版本和入口命令的干净环境执行。脚本不得依赖图形客户端自动设置、个人默认数据库或先前会话变量;所有对象名、字符集、事务和预期错误都应明确。对仅用于说明、不可直接执行的片段要标注上下文,避免把省略条件的示意 SQL 当作完整迁移。

还要保存一份“结果为什么可信”的说明:约束证明哪些坏数据无法进入,事务证明哪些变化共同提交,执行计划证明访问了哪些行,权限测试证明哪些身份无法越界,恢复演练证明故障后能重新得到可用状态。这五类证据缺一时,都应在结论旁写明限制。数据库实验的价值不只是得到答案,而是让另一位操作者在不同机器、不同时间仍能重现同一判断。

若结论依赖当前数据分布或配置,应把适用范围写在 SQL 旁,并安排数据增长或版本升级后的复测条件;不要把一次测量永久固化为规则。


九、StudyStore 实验

编写 StudyStore 前三份不可变迁移,在两个全新数据库中分别执行并比较 SHOW CREATE TABLE。制造一个重复 SKU 和无效外键,确认约束拒绝;开启长事务后观察 ALTER 的元数据锁等待并安全终止实验。

完成本节后,不要只保存代码或 SQL。请同时保存执行命令、关键输出和失败案例;学习笔记真正有价值的部分,是能够说明输入、状态变化、输出以及失败后的恢复方式。


十、常见错误

  • 发布后修改旧迁移文件,导致环境历史分叉
  • 滥用 IF NOT EXISTS 掩盖对象定义差异
  • 默认所有父子关系都适合级联删除
  • 在小测试表上宣称大表 ALTER 无锁
  • 假定所有 DDL 都能随事务完整回滚

十一、练习与自测

  1. 为六张表写命名一致的主键、外键、唯一键和 CHECK。
  2. 比较 RESTRICT、CASCADE 与 SET NULL 的业务后果。
  3. 设计新增 NOT NULL 列的兼容分阶段迁移。
  4. 用两个会话重现元数据锁等待并查询阻塞来源。

自测时应在干净的临时目录或临时数据库中重新执行,而不是依赖上一节遗留的状态。如果结果与预期不同,先记录实际输出,再缩小问题范围。


十二、官方资料

版本行为与二手文章不一致时,以本系列固定版本的官方文档、命令输出和可重复测试结果为准。

上一篇:关系模型、数据类型与字符集 下一篇:SELECT、表达式、NULL 与分页