1. MySQL表约束的本质与价值
刚接触数据库设计时,我经常困惑为什么有些字段明明定义了INT类型,插入字符串却会报错。直到有次线上事故让我彻底明白——没有约束的数据库就像没有交通规则的十字路口。MySQL的表约束机制,正是保证数据完整性的核心防线。
约束(Constraint)本质是附着在表结构上的数据校验规则。当我们在电商系统创建用户表时,要求手机号必须唯一、余额不能为负、性别只能是预设选项——这些业务规则都需要通过约束来实现。与应用程序中的校验不同,数据库层面的约束具有以下不可替代性:
- 原子性保障:在事务处理中,约束检查发生在语句执行后、事务提交前。这意味着即便程序存在逻辑漏洞,脏数据也无法突破数据库的防御
- 统一标准:无论通过什么渠道操作数据(API、命令行、可视化工具),约束规则始终生效
- 性能优势:主键、外键等约束会自动创建索引,大幅提升查询效率
实际教训:曾有个用户注册接口因未校验手机号唯一性,导致出现重复账号。后来在数据库添加UNIQUE约束后,即便程序有BUG,数据库也会拒绝重复数据
2. 五大核心约束详解
2.1 非空约束(NOT NULL)
最基础的约束类型,强制字段必须有值。在创建表时,建议所有业务关键字段都应显式声明NOT NULL:
CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, -- 必须提供用户名 password CHAR(60) NOT NULL, -- 密码不允许为空 avatar_url VARCHAR(255) -- 头像允许NULL );实现原理:MySQL会在插入或更新时检查该列值是否为NULL。如果违反规则,会立即抛出错误代码1048(Column cannot be null)
注意事项:
- 所有NULL值会额外占用1字节存储空间(NULL标志位)
- 聚合函数如COUNT()会忽略NULL值,但COUNT(*)不会
- 使用IS NULL/IS NOT NULL进行条件判断时,要避免在索引列上使用NOT IN (NULL)这种错误写法
2.2 唯一约束(UNIQUE)
确保某列或多列组合的值不重复,常用于手机号、邮箱等业务唯一标识:
CREATE TABLE employees ( emp_id INT PRIMARY KEY, emp_code VARCHAR(10) UNIQUE, -- 工号唯一 mobile CHAR(11) UNIQUE, -- 手机号唯一 department VARCHAR(50) );特殊用法:可以创建多列联合唯一约束。比如会议室预订系统需要保证同一时段同一会议室只能有一个预订:
CREATE TABLE meeting_booking ( id INT PRIMARY KEY, room_id INT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, UNIQUE KEY (room_id, start_time) -- 联合唯一约束 );踩坑记录:早期项目曾用用户名作为UNIQUE约束,后来发现不同分公司可以有同名员工。最终解决方案是改为company_id + username的联合唯一约束
2.3 主键约束(PRIMARY KEY)
主键是特殊的唯一约束,具有以下特性:
- 不允许NULL值(而UNIQUE允许单个NULL)
- 每个表只能有一个主键
- 默认自动创建聚簇索引(InnoDB引擎)
自增主键的最佳实践:
CREATE TABLE products ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, -- 推荐写法 sku VARCHAR(20) NOT NULL, price DECIMAL(10,2) NOT NULL );复合主键的应用场景:学生选课系统的关联表通常需要双主键设计:
CREATE TABLE student_courses ( student_id INT NOT NULL, course_id INT NOT NULL, score TINYINT UNSIGNED, PRIMARY KEY (student_id, course_id) -- 复合主键 );2.4 默认约束(DEFAULT)
当插入数据未指定值时,自动填充预设值。合理使用DEFAULT能简化应用代码:
CREATE TABLE articles ( id INT PRIMARY KEY, title VARCHAR(100) NOT NULL, content TEXT NOT NULL, status ENUM('draft','published','archived') DEFAULT 'draft', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 自动记录创建时间 view_count INT UNSIGNED DEFAULT 0 );动态默认值:MySQL 8.0+支持表达式作为默认值:
CREATE TABLE transactions ( id INT PRIMARY KEY, amount DECIMAL(12,2) NOT NULL, fee DECIMAL(10,2) DEFAULT (amount * 0.01), -- 动态计算手续费 created_at DATETIME DEFAULT (NOW()) );2.5 外键约束(FOREIGN KEY)
建立表间关联关系,确保引用完整性。典型用例是订单关联用户:
CREATE TABLE orders ( order_id INT PRIMARY KEY, user_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT -- 阻止删除被引用的用户 ON UPDATE CASCADE -- 用户id更新时自动同步 );外键动作说明:
- RESTRICT:默认行为,阻止破坏引用完整性的操作
- CASCADE:主表变更时,从表自动同步
- SET NULL:主表记录删除后,从表外键设为NULL
- NO ACTION:与RESTRICT类似,但检查时机略有不同
性能考量:
- 外键会带来额外的锁开销,高并发写入场景需谨慎
- 大型系统有时会在应用层实现外键逻辑,换取更高吞吐量
- 使用外键时,务必确保关联字段有索引(MySQL会自动创建)
3. 高级约束技巧与优化
3.1 CHECK约束(MySQL 8.0+)
虽然MySQL长期缺乏标准CHECK约束,但8.0版本终于完整支持:
CREATE TABLE bank_accounts ( account_id INT PRIMARY KEY, balance DECIMAL(15,2) NOT NULL, currency CHAR(3) NOT NULL, CONSTRAINT chk_balance CHECK (balance >= 0), -- 余额不能为负 CONSTRAINT chk_currency CHECK (currency IN ('CNY','USD','EUR')) );模拟方案(5.7及以下版本):
- 使用触发器实现类似功能
- 通过ENUM类型限制取值范围
- 在应用层进行严格校验
3.2 约束命名规范
显式命名约束有助于维护和错误排查:
CREATE TABLE flight_seats ( seat_id VARCHAR(5) PRIMARY KEY, flight_no VARCHAR(10) NOT NULL, class ENUM('economy','business','first') NOT NULL, passenger_id INT, CONSTRAINT fk_passenger FOREIGN KEY (passenger_id) REFERENCES passengers(id), CONSTRAINT uk_seat UNIQUE (flight_no, seat_id), CONSTRAINT chk_seat_format CHECK (seat_id REGEXP '^[A-Z][1-9][0-9]?$') );3.3 约束与索引的协同
大多数约束会自动创建索引:
- PRIMARY KEY:聚簇索引
- UNIQUE:唯一索引
- FOREIGN KEY:普通索引(如果不存在)
验证索引效果:
-- 查看表的索引情况 SHOW INDEX FROM orders; -- 强制不使用索引(测试约束失效情况) SELECT * FROM orders IGNORE INDEX(user_id) WHERE user_id = 100;4. 约束管理实战
4.1 约束的增删改查
添加约束(表已存在):
-- 添加NOT NULL约束 ALTER TABLE products MODIFY COLUMN stock INT NOT NULL; -- 添加UNIQUE约束 ALTER TABLE employees ADD CONSTRAINT uk_email UNIQUE (email); -- 添加外键约束 ALTER TABLE order_items ADD CONSTRAINT fk_product FOREIGN KEY (product_id) REFERENCES products(id);删除约束:
-- 删除主键(需先删除自增属性) ALTER TABLE temp_table MODIFY id INT; ALTER TABLE temp_table DROP PRIMARY KEY; -- 删除外键 ALTER TABLE orders DROP FOREIGN KEY fk_user;4.2 约束冲突处理
批量导入时临时禁用约束:
-- 禁用外键检查 SET FOREIGN_KEY_CHECKS = 0; -- 执行大量数据导入 LOAD DATA INFILE '/tmp/users.csv' INTO TABLE users; -- 重新启用检查 SET FOREIGN_KEY_CHECKS = 1;错误排查示例:
-- 查看最近发生的约束错误 SELECT * FROM performance_schema.events_errors_summary_global_by_error WHERE error_name LIKE '%CONSTRAINT%'; -- 解析错误代码 SHOW ERRORS;5. 性能优化与最佳实践
5.1 约束带来的性能影响
- 写入性能:每次INSERT/UPDATE都需要检查所有相关约束
- 解决方案:批量操作时临时禁用约束
- 锁争用:外键约束可能导致表级锁
- 优化方案:使用InnoDB引擎+合理事务隔离级别
- 索引维护:约束关联的索引会增加存储和IO开销
基准测试对比:
| 约束类型 | 无约束TPS | 有约束TPS | 下降幅度 |
|---|---|---|---|
| 无约束 | 12,345 | - | - |
| PRIMARY KEY | 11,200 | 9% | |
| FOREIGN KEY | 8,500 | 31% | |
| 多重约束 | 6,700 | 46% |
5.2 设计建议
- 早期严格:开发阶段应启用所有约束,捕获设计缺陷
- 生产环境权衡:根据业务特点选择性使用约束
- 命名规范:采用
约束类型_表名_字段名的命名方式(如fk_orders_user_id) - 文档记录:在数据字典中记录所有约束的业务含义
混合策略案例:
- 核心业务表:启用完整约束
- 日志类表:仅用NOT NULL
- 数据仓库:应用层校验为主
6. 常见问题解决方案
6.1 ERROR 1452外键冲突
场景:删除用户时出现"Cannot delete or update a parent row: a foreign key constraint fails"
解决方案:
- 先删除关联的子表记录
- 设置ON DELETE CASCADE
- 临时禁用外键检查
-- 方案示例 BEGIN; SET FOREIGN_KEY_CHECKS = 0; DELETE FROM users WHERE id = 101; SET FOREIGN_KEY_CHECKS = 1; COMMIT;6.2 自增主键跳号问题
现象:事务回滚后,AUTO_INCREMENT值不连续
技术本质:InnoDB的计数器存储在内存中,不保证连续
应对策略:
- 业务上不要依赖ID连续性
- 如需严格连续,改用程序生成ID
- 重大操作前执行
ALTER TABLE ... AUTO_INCREMENT=xxx重置
6.3 ENUM约束变更
挑战:修改ENUM选项会导致表重建
平滑迁移方案:
- 新增允许NULL的临时列
- 分批更新数据
- 删除旧列,重命名新列
ALTER TABLE products ADD COLUMN size_new ENUM('S','M','L','XL') NULL; UPDATE products SET size_new = size WHERE size IN ('S','M','L'); UPDATE products SET size_new = 'XL' WHERE size = 'X-Large'; ALTER TABLE products DROP COLUMN size; ALTER TABLE products CHANGE COLUMN size_new size ENUM('S','M','L','XL') NOT NULL;7. 真实案例:电商系统约束设计
7.1 商品表约束矩阵
CREATE TABLE products ( product_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, sku VARCHAR(20) NOT NULL, name VARCHAR(100) NOT NULL, category ENUM('electronics','clothing','books') NOT NULL, price DECIMAL(10,2) NOT NULL CHECK (price > 0), stock INT UNSIGNED NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT uk_sku UNIQUE (sku), CONSTRAINT chk_stock CHECK (stock >= 0) ) ENGINE=InnoDB;7.2 订单业务约束链
-- 订单主表 CREATE TABLE orders ( order_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, user_id INT UNSIGNED NOT NULL, order_status ENUM('pending','paid','shipped','completed','cancelled') NOT NULL DEFAULT 'pending', total_amount DECIMAL(12,2) NOT NULL CHECK (total_amount >= 0), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(user_id) ) ENGINE=InnoDB; -- 订单明细 CREATE TABLE order_items ( item_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, order_id BIGINT UNSIGNED NOT NULL, product_id INT UNSIGNED NOT NULL, quantity SMALLINT UNSIGNED NOT NULL CHECK (quantity > 0), unit_price DECIMAL(10,2) NOT NULL CHECK (unit_price >= 0), FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(product_id), CONSTRAINT uk_order_product UNIQUE (order_id, product_id) ) ENGINE=InnoDB;7.3 支付系统约束设计
CREATE TABLE payments ( payment_id VARCHAR(32) PRIMARY KEY, -- 支付平台流水号 order_id BIGINT UNSIGNED NOT NULL, amount DECIMAL(10,2) NOT NULL CHECK (amount > 0), payment_method ENUM('alipay','wechat','credit_card') NOT NULL, status ENUM('init','processing','success','failed') NOT NULL DEFAULT 'init', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP NULL, FOREIGN KEY (order_id) REFERENCES orders(order_id), CONSTRAINT chk_timestamp CHECK (completed_at IS NULL OR completed_at >= created_at) );8. 监控与维护
8.1 约束违规监控
-- 创建审计表记录约束错误 CREATE TABLE constraint_violations ( id INT AUTO_INCREMENT PRIMARY KEY, table_name VARCHAR(64) NOT NULL, constraint_name VARCHAR(64), error_message TEXT, sql_text TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 设置触发器捕获错误 DELIMITER // CREATE TRIGGER after_error_log AFTER ERROR ON *.* FOR EACH STATEMENT BEGIN IF (IS_ERROR(1452, 1062, 1048)) THEN -- 常见约束错误代码 INSERT INTO constraint_violations VALUES (NULL, ERROR_TABLE(), ERROR_CONSTRAINT_NAME(), ERROR_MESSAGE(), ERROR_SQL_TEXT(), NOW()); END IF; END// DELIMITER ;8.2 约束有效性检查
定期验证外键关系完整性:
SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = 'your_database';8.3 性能影响分析
使用Performance Schema监控约束检查开销:
-- 查看约束相关的等待事件 SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT/1000000000 AS wait_sec FROM performance_schema.events_waits_summary_global_by_event_name WHERE EVENT_NAME LIKE '%constraint%' ORDER BY wait_sec DESC;9. 版本兼容性指南
9.1 MySQL 5.7与8.0差异
| 特性 | 5.7支持情况 | 8.0改进点 |
|---|---|---|
| CHECK约束 | 语法支持但不生效 | 完全功能支持 |
| 表达式默认值 | 仅支持常量 | 支持函数和表达式 |
| 外键原子性 | 可能部分成功 | 完全原子操作 |
| 约束命名空间 | 全局唯一 | 表级别唯一 |
9.2 迁移注意事项
CHECK约束迁移:
- 5.7中伪CHECK约束需要转换为触发器
- 8.0中可保留原有语法并实际生效
外键算法变化:
- 8.0默认使用更高效的算法
- 可通过
foreign_key_checks=OFF临时禁用
性能调整:
- 8.0对约束检查有优化,通常无需特殊配置
- 监控
handler_%计数器评估约束开销
10. 替代方案与边界案例
10.1 应用层校验的取舍
适用场景:
- 需要复杂业务规则(如"VIP用户折扣不超过30%")
- 跨数据库兼容性要求高
- 高频写入且对性能极度敏感
实现示例(Java):
public class OrderValidator { public void validate(Order order) { if (order.getItems().isEmpty()) { throw new ConstraintViolationException("订单必须包含商品"); } if (order.getTotal().compareTo(BigDecimal.ZERO) <= 0) { throw new ConstraintViolationException("金额必须大于零"); } } }10.2 存储过程封装
对于需要事务性保证的复杂约束:
DELIMITER // CREATE PROCEDURE place_order( IN p_user_id INT, IN p_product_id INT, IN p_quantity INT ) BEGIN DECLARE v_stock INT; DECLARE v_price DECIMAL(10,2); -- 检查库存 SELECT stock, price INTO v_stock, v_price FROM products WHERE product_id = p_product_id FOR UPDATE; IF v_stock < p_quantity THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '库存不足'; END IF; -- 扣减库存 UPDATE products SET stock = stock - p_quantity WHERE product_id = p_product_id; -- 创建订单 INSERT INTO orders (...) VALUES (...); COMMIT; END// DELIMITER ;10.3 触发器方案
当CHECK约束无法满足需求时:
CREATE TRIGGER validate_salary BEFORE INSERT ON employees FOR EACH ROW BEGIN IF NEW.salary < (SELECT min_salary FROM positions WHERE id = NEW.position_id) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '薪资低于岗位最低标准'; END IF; END;11. 未来发展与替代技术
11.1 MySQL约束的演进方向
- 延迟约束检查:类似PostgreSQL的DEFERRABLE特性
- 跨表CHECK约束:当前仅支持单表内校验
- 更细粒度的约束禁用:按会话或事务临时禁用特定约束
11.2 分布式数据库的挑战
在分库分表场景下,传统约束面临的问题:
- 外键无法跨物理节点
- 唯一约束需要全局协调
- CHECK约束可能在不同节点重复计算
解决方案趋势:
- 使用分布式事务(如XA协议)
- 最终一致性+补偿机制
- 专用全局唯一ID服务
12. 决策树:如何选择合适的约束
根据业务需求选择约束策略:
是否需要保证字段必有值? ├─ 是 → 添加NOT NULL约束 └─ 否 → 允许NULL │ │是否需要值唯一? ├─ 是 → 添加UNIQUE约束 └─ 否 → 普通字段 │ │是否需要关联其他表? ├─ 是 → 添加FOREIGN KEY └─ 否 → 检查业务规则 │ │是否需要复杂校验? ├─ 是 → 使用CHECK或触发器 └─ 否 → 完成13. 性能测试方法论
13.1 基准测试设计
测试场景:
- 纯插入性能(单线程/多线程)
- 混合读写负载
- 批量导入场景
关键指标:
- TPS(每秒事务数)
- 平均延迟
- 约束错误率
13.2 测试用例示例
-- 测试表1:无约束 CREATE TABLE test_no_constraints ( id INT, name VARCHAR(50), value INT ); -- 测试表2:基础约束 CREATE TABLE test_basic_constraints ( id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, value INT CHECK (value > 0) ); -- 测试脚本(使用sysbench) sysbench oltp_insert \ --mysql-host=127.0.0.1 \ --mysql-port=3306 \ --mysql-user=test \ --mysql-password=test \ --mysql-db=benchmark \ --tables=1 \ --table-size=1000000 \ --threads=32 \ --time=300 \ --report-interval=10 \ run14. 疑难解答手册
14.1 错误代码速查
| 错误代码 | 含义 | 解决方案 |
|---|---|---|
| 1062 | 唯一键冲突 | 检查重复值或调整业务逻辑 |
| 1452 | 外键约束失败 | 确保引用数据存在 |
| 1048 | 非空约束违反 | 提供必填字段值 |
| 3819 | CHECK约束违反 | 检查数据是否符合业务规则 |
| 1216 | 外键级联操作导致循环 | 检查外键引用链 |
14.2 锁争用分析
当约束导致锁等待时:
-- 查看当前锁等待 SELECT * FROM performance_schema.events_waits_current WHERE EVENT_NAME LIKE '%lock%'; -- 分析外键锁问题 SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread FROM information_schema.innodb_lock_waits w INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;15. 资源消耗与调优
15.1 约束的内存占用
不同约束类型的额外开销:
| 约束类型 | 每行额外存储 | 内存消耗 |
|---|---|---|
| PRIMARY KEY | 0 bytes | 低 |
| UNIQUE | 4-8 bytes | 中 |
| FOREIGN KEY | 8-12 bytes | 中高 |
| CHECK | 0 bytes | 取决于复杂度 |
15.2 配置参数优化
# my.cnf 优化建议 [mysqld] # 外键检查缓冲池 foreign_key_checks_buffer_size = 64M # 约束检查线程数 constraint_check_threads = 4 # 禁用不必要的约束检查(开发环境) # skip-constraint-checks = OFF16. 行业实践对比
16.1 MySQL vs PostgreSQL约束特性
| 特性 | MySQL | PostgreSQL |
|---|---|---|
| CHECK约束 | 8.0+完整支持 | 长期支持 |
| 排除约束 | 不支持 | 支持 |
| 延迟约束 | 不支持 | DEFERRABLE |
| 约束触发器 | 需手动创建 | 内置规则系统 |
16.2 云数据库服务差异
| 服务商 | 约束特性限制 | 特殊功能 |
|---|---|---|
| AWS RDS | 无特殊限制 | 支持跨Region外键 |
| Azure Database | 禁用部分系统表约束 | 提供约束建议工具 |
| 阿里云RDS | 外键级联操作有配额限制 | 支持约束违规告警 |
17. 自动化工具推荐
17.1 约束文档生成
使用mysqldump提取约束定义:
mysqldump --no-data -d your_database | grep -E "CONSTRAINT|PRIMARY KEY|FOREIGN KEY"17.2 可视化工具
- MySQL Workbench:图形化显示约束关系
- Navicat:支持约束的批量管理
- DBeaver:跨平台的约束分析工具
17.3 代码生成框架
MyBatis Generator配置示例:
<table tableName="products"> <columnOverride column="price" constraints="NOT NULL, CHECK (price > 0)"/> <generatedKey column="product_id" identity="true" sqlStatement="MySQL"/> </table>18. 安全加固建议
18.1 约束注入防护
防止恶意用户通过精心构造的数据绕过约束:
- 始终使用参数化查询
- 应用层和数据库层双重校验
- 定期审计约束有效性
18.2 权限控制
-- 创建仅能读取约束元数据的角色 CREATE ROLE constraint_auditor; GRANT SELECT ON information_schema.TABLE_CONSTRAINTS TO constraint_auditor; GRANT SELECT ON information_schema.KEY_COLUMN_USAGE TO constraint_auditor;19. 监控指标体系建设
19.1 关键监控项
约束违规率:
SELECT SUM(IF(error_number IN (1048,1062,1452),1,0)) AS constraint_errors, COUNT(*) AS total_errors, (SUM(IF(error_number IN (1048,1062,1452),1,0))/COUNT(*))*100 AS error_ratio FROM performance_schema.events_errors_summary_global_by_error;约束检查延迟:
SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT/COUNT_STAR/1000000 AS avg_ms FROM performance_schema.events_waits_summary_global_by_event_name WHERE EVENT_NAME LIKE '%constraint%';
19.2 报警规则示例
Prometheus监控规则片段:
groups: - name: mysql_constraints rules: - alert: HighConstraintViolations expr: rate(mysql_global_status_errors{error_type=~"1048|1062|1452"}[5m]) > 10 for: 10m labels: severity: warning annotations: summary: "High rate of constraint violations" description: "{{ $value }} constraint violations per second"20. 演进式设计策略
20.1 约束变更管理
安全变更流程:
- 在测试环境验证新约束
- 使用
pt-online-schema-change在线变更 - 分批验证数据合规性
- 监控生产环境性能影响
20.2 版本控制方案
将约束定义纳入数据库版本管理:
-- V1__initial_constraints.sql ALTER TABLE users ADD CONSTRAINT chk_email_format CHECK (email REGEXP '^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}$'); -- V2__add_foreign_keys.sql ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id);20.3 灰度发布方案
-- 第一阶段:仅记录违规不阻止 SET @old_sql_mode = @@sql_mode; SET SESSION sql_mode = 'NO_ENGINE_SUBSTITUTION'; -- 第二阶段:采样检查 CREATE TABLE temp_constraint_check AS SELECT id FROM products WHERE price <= 0 LIMIT 1000; -- 第三阶段:全量启用 SET SESSION sql_mode = @old_sql_mode;