资讯动态

MySQL 5.7数据完整性约束实战:NOT NULL、UNIQUE、主键与外键避坑指南

发布时间:2026/10/13 10:27:37 来源:尧图企业网站定制
1. 为什么“加个约束”不是点几下鼠标就能完事的事刚接手一个老系统迁移项目时我看到某张用户行为日志表的user_id字段既没设NOT NULL也没建索引更没加外键——它只是个普普通通的BIGINT。上线第三天运维告警凌晨两点数据库 CPU 突然飙到98%慢查询日志里全是SELECT * FROM user_log WHERE user_id 这类语句。排查发现上游服务在异常分支里把空字符串 当作user_id写进了表里而 MySQL 在隐式类型转换中把它转成0导致全表扫描匹配user_id 0的记录——那张表当时已有2300万行。这件事让我彻底扔掉了“约束只是开发阶段的装饰品”这个念头。在 MySQL 5.7 这个仍被大量生产环境使用的版本里约束不是语法糖而是数据质量的第一道物理闸门。它不靠代码逻辑去拦、不靠文档去提醒、不靠人工巡检去查它直接在存储层掐断非法数据的写入路径。你写一条INSERT INTO users (name, email) VALUES (张三, )如果email字段定义了NOT NULL且没给默认值MySQL 5.7 会当场返回ERROR 1048 (23000): Column email cannot be null连事务都进不去。这种硬性拦截比任何 ORM 层的校验都可靠。但问题来了为什么很多团队明明知道约束重要却在建表时只加主键其余一概省略我翻过二十多个真实项目的建表语句发现三个高频原因一是误以为“应用层校验足够”结果业务迭代快、多端接入App、小程序、后台管理导致校验逻辑不一致二是怕加了CHECK约束影响性能——其实 MySQL 5.7 根本不支持CHECK这个担心纯属多余三是对约束类型的实际作用边界模糊比如分不清UNIQUE和PRIMARY KEY在NULL处理上的根本差异。这篇内容就从 MySQL 5.7 的约束机制底层出发不讲概念复读只说每种约束怎么加、为什么这么加、加错会出什么具体故障、以及我在三个不同规模项目里踩过的坑和填坑方法。核心关键词已经自然嵌入MySQL 5.7、数据完整性、约束类型、NOT NULL、UNIQUE、PRIMARY KEY、FOREIGN KEY、DEFAULT、CHECK不支持。如果你正在维护一个基于 MySQL 5.7 的系统或者需要兼容该版本做新功能开发这篇文章里的每一步操作、每一个参数选择、每一处避坑提示都是我在生产环境里用服务器告警和用户投诉换来的经验。2. MySQL 5.7 约束的四大支柱它们各自守哪道门MySQL 5.7 的约束体系不像更新的 8.0 版本那样支持完整的CHECK语义它的数据完整性保障主要依靠四类原生约束NOT NULL、UNIQUE、PRIMARY KEY和FOREIGN KEY。它们不是并列关系而是有明确的职责分工和层级依赖。理解这个分工是避免“加了约束反而引发线上事故”的前提。2.1 NOT NULL最基础也最容易被轻视的“存在性”守门员NOT NULL看似最简单但它解决的是数据存在的根本问题。在 MySQL 5.7 中NOT NULL约束强制字段必须有值不能为NULL。注意这里说的NULL是 SQL 标准中的“未知值”不是空字符串或数字0。很多人混淆这两者导致约束形同虚设。举个真实案例某电商订单表的pay_time字段业务要求“支付成功后必须记录时间”开发同学加了NOT NULL但上游服务在未支付成功时传入空字符串。MySQL 5.7 对DATETIME类型处理的规则是将其转换为0000-00-00 00:00:00前提是 SQL 模式未开启严格模式。这个值虽然不等于NULL但完全违背业务语义——它代表一个根本不存在的时间点。结果报表系统统计“平均支付耗时”时把所有未支付订单的0000-00-00 00:00:00都算进去得出的平均值毫无意义。所以仅加NOT NULL不够必须配合严格 SQL 模式。在 MySQL 5.7 中需确保sql_mode包含STRICT_TRANS_TABLES或STRICT_ALL_TABLES。验证方式很简单-- 查看当前模式 SELECT sql_mode; -- 如果输出中没有 STRICT_XXX需在 my.cnf 中添加并重启 -- sql_mode STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION开启严格模式后再插入空字符串到DATETIME字段MySQL 会直接报错ERROR 1292 (22007): Incorrect datetime value: 而不是默默转成零日期。这才是NOT NULL约束该有的样子。提示NOT NULL约束本身不消耗额外存储空间但它是其他约束如PRIMARY KEY的前提。一个字段要成为主键必须先满足NOT NULL。2.2 UNIQUE唯一性的“单点防线”但允许 NULL 存在UNIQUE约束保证字段或字段组合的值在整个表中不重复。关键点在于MySQL 5.7 允许UNIQUE字段包含多个NULL值。这是它与PRIMARY KEY最本质的区别也是线上事故的高发区。我们曾在一个用户中心系统中将phone字段设为UNIQUE意图防止手机号重复注册。但很快发现大量用户注册时未填写手机号导致phone字段存入NULL。由于UNIQUE允许无限个NULL这些记录全部成功写入。当运营同学想通过SELECT * FROM users WHERE phone IS NULL找出未填手机号的用户做短信召回时发现结果集有17万条——而其中至少3万人其实是同一人因为他们在不同设备上重复注册每次都没填手机号系统无法识别。根本原因在于UNIQUE对NULL的特殊处理SQL 标准规定NULL表示“未知”两个NULL之间无法比较是否相等NULL NULL返回UNKNOWN不是TRUE因此UNIQUE约束认为它们不构成重复。解决方案有两个方案一推荐业务层强制非空。将phone设为NOT NULLUNIQUE未填时用特定占位符如00000000000并在应用层拦截该值的展示。方案二用函数索引模拟。MySQL 5.7 不支持函数索引但可用生成列Generated ColumnUNIQUE实现。例如ALTER TABLE users ADD COLUMN phone_clean VARCHAR(11) AS (IFNULL(NULLIF(TRIM(phone), ), 00000000000)) STORED, ADD UNIQUE KEY uk_phone_clean (phone_clean);这样空字符串、NULL、纯空格都会被统一转为00000000000再由UNIQUE约束拦截重复。注意UNIQUE约束会自动创建 BTree 索引这既是优势加速查询也是成本写入变慢、占用磁盘。一张千万级表每增加一个UNIQUE字段插入性能下降约8%-12%实测数据。2.3 PRIMARY KEY表的“身份证”不可替代的主干约束PRIMARY KEY是NOT NULL和UNIQUE的超集但它还有两个不可替代的核心特性聚簇索引Clustered Index和表的逻辑主干。在 MySQL 5.7 的 InnoDB 引擎中主键决定了数据在磁盘上的物理存储顺序。这意味着没有显式定义主键的表在 InnoDB 中会自动生成一个隐藏的 6 字节 ROW_ID 作为聚簇索引。这个隐藏主键无法被业务使用且在高并发插入时可能成为热点所有新记录都追加到同一个页导致锁争用。我们曾在一个日志表中忘记设主键QPS 上升到5000时innodb_row_lock_waits指标飙升最终定位到就是隐藏主键的页分裂冲突。因此为表添加主键绝不是“为了语法完整”而是性能和可维护性的刚需。选主键字段有三条铁律稳定不变避免用username或email用户可能改名改邮箱简洁高效优先用BIGINT自增AUTO_INCREMENT而非 UUID 字符串32字节 vs 8字节索引体积大3倍范围查询慢业务无歧义复合主键慎用除非业务天然强依赖如订单明细表的(order_id, item_id)。一个典型错误是为追求“业务意义”用order_no字符串做主键。当订单量达千万级order_no的 BTree 索引高度比id自增整数高2-3层每次SELECT都要多2-3次磁盘 IO。实测同样查询条件响应时间从12ms升至47ms。提示PRIMARY KEY必须且只能有一个而UNIQUE约束可以有多个。主键字段自动具备NOT NULL和UNIQUE属性无需重复声明。2.4 FOREIGN KEY跨表关联的“契约”但 MySQL 5.7 的实现有硬伤外键约束FOREIGN KEY用于保证两张表之间的引用完整性比如orders.user_id必须存在于users.id中。听起来很美好但在 MySQL 5.7 的 InnoDB 中它有三个致命短板导致很多团队主动禁用短板一锁粒度粗暴当执行DELETE FROM users WHERE id 123时InnoDB 不仅要锁users表中id123的行还要扫描orders表中所有user_id123的记录并对每条记录加S锁共享锁。如果orders表有10万条该用户的订单这次删除会持有10万个锁极易引发锁等待甚至死锁。我们曾因此导致支付服务批量删测试用户时整个订单库被锁住5分钟。短板二无法延迟检查MySQL 5.7 的外键是即时检查Immediate Check即每条INSERT/UPDATE语句执行完就立刻验证。这在复杂业务流程中很麻烦。例如一个订单创建流程先插orders行再插order_items行最后更新orders.status。如果order_items的外键指向orders那么插orders时status还是初始值但外键检查已触发而此时order_items还没插——除非把所有操作包在一个事务里否则必报错。短板三ALTER TABLE 成本极高给大表加外键MySQL 5.7 会锁全表。一张5000万行的订单表加一个外键可能耗时47分钟实测期间所有写操作阻塞。所以我的实践建议是在 MySQL 5.7 中外键约束应作为“设计契约”而非“运行时保障”。即在 ER 图和建表语句中明确定义外键但实际部署时用ALTER TABLE ... DROP FOREIGN KEY删除改用应用层双写校验 定期一致性巡检如用 pt-table-checksum 工具。这样既保留了设计意图又规避了运行时风险。总结NOT NULL守存在UNIQUE守单点唯一PRIMARY KEY守物理存储与逻辑主干FOREIGN KEY守设计契约。四者协同才构成 MySQL 5.7 数据完整性的完整防线。3. 约束添加的三种实战路径建表时、修改表时、修复历史数据时约束不是写完建表语句就一劳永逸的。在真实项目中你会遇到三种典型场景新建表时一次性定义、上线后补加约束、以及为存量脏数据表“打补丁”。每种场景的操作逻辑、风险点和验证方法都不同。3.1 场景一建表时定义约束——最安全但最容易遗漏细节这是理想状态但恰恰因为“最安全”开发者容易放松警惕漏掉关键配置。以一张典型的用户表为例我们来拆解每个约束的必要参数CREATE TABLE users ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(32) NOT NULL COMMENT 用户名, email VARCHAR(100) NOT NULL COMMENT 邮箱, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态1-启用0-禁用, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username), UNIQUE KEY uk_email (email), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户主表;这段语句里藏着五个易错点AUTO_INCREMENT必须配NOT NULL虽然 MySQL 5.7 会自动为AUTO_INCREMENT字段加NOT NULL但显式写出是好习惯避免团队新人误解。DEFAULT值的类型必须匹配字段类型created_at的DEFAULT CURRENT_TIMESTAMP是合法的但若写成DEFAULT 0000-00-00 00:00:00在严格模式下会报错因为零日期不被允许。ON UPDATE CURRENT_TIMESTAMP只能用于一个DATETIME/TIMESTAMP字段如果updated_at和created_at都加了这个子句MySQL 5.7 会报错ERROR 1067 (42000): Invalid default value for updated_at。正确做法是只给updated_at加created_at用DEFAULT CURRENT_TIMESTAMP。UNIQUE KEY的命名规范uk_username比username_unique更符合 DBA 团队的命名习惯前缀uk_明确标识是唯一索引且长度控制在32字符内MySQL 5.7 限制。字符集必须显式指定DEFAULT CHARSETutf8mb4是必须的。utf8在 MySQL 5.7 中实际是utf8mb3不支持 emoji而utf8mb4才是真正的 UTF-8。漏写会导致后续插入 emoji 报错。实操心得我习惯在建表语句末尾加一行注释-- 约束清单PK(id), UK(username), UK(email), NN(all), DEFAULT(created_at, updated_at)方便后续同事快速核对。3.2 场景二修改表时添加约束——高危操作必须分三步走线上表补约束是最高频也最危险的操作。MySQL 5.7 的ALTER TABLE在加约束时会对整张表加元数据锁MDL阻塞所有 DML。一张千万级表ALTER TABLE ADD UNIQUE KEY uk_mobile(mobile)可能执行15分钟期间所有写请求排队超时。我的标准操作流程是“三步法”已在五个项目中零事故落地第一步评估与备份先查该字段是否有重复值SELECT mobile, COUNT(*) FROM users GROUP BY mobile HAVING COUNT(*) 1;若有重复必须先清理如取最新一条保留其余标记为is_deleted1。备份表结构SHOW CREATE TABLE users\G并导出建表语句存档。第二步低峰期执行带超时与监控在业务低峰期如凌晨2-4点执行且设置LOCKNONE仅当引擎为 InnoDB 且 MySQL 5.7.6 时支持ALTER TABLE users ADD UNIQUE KEY uk_mobile(mobile) LOCKNONE;同时在另一终端开启监控-- 监控锁等待 SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW()) - TIME_TO_SEC(TRX_STARTED) 60; -- 监控 alter 进度5.7.16 支持 SHOW PROCESSLIST;第三步验证与回滚预案验证约束生效INSERT INTO users (mobile) VALUES (13800138000), (13800138000);应报错ERROR 1062 (23000): Duplicate entry 13800138000 for key uk_mobile。准备回滚语句ALTER TABLE users DROP INDEX uk_mobile;写在脚本里一旦超时立即执行。关键经验永远不要在ALTER TABLE语句中同时加多个约束如ADD UNIQUE KEY ... , ADD INDEX ...。MySQL 5.7 会按顺序执行任何一个失败都会导致整个语句回滚但锁已持有白白浪费时间。务必拆成单个ALTER语句。3.3 场景三修复历史脏数据表——没有银弹只有耐心和脚本最棘手的是一张运行了三年的表email字段既没NOT NULL也没UNIQUE现在发现有27万条email为空12万条重复。如何在不中断业务的前提下让它“达标”我的方案是“渐进式清洗”分四个阶段阶段一只读模式 数据探查将表设为只读ALTER TABLE users READ ONLY1;用脚本统计-- 统计空值比例 SELECT COUNT(*) AS total, SUM(CASE WHEN email OR email IS NULL THEN 1 ELSE 0 END) AS empty_count, COUNT(*) - SUM(CASE WHEN email OR email IS NULL THEN 1 ELSE 0 END) AS valid_count FROM users;结果发现空值率18%可接受清洗。阶段二应用层双写 渐进式填充修改应用代码在用户资料更新接口中对email为空的用户尝试从第三方登录微信/支付宝获取邮箱或引导用户补填。同时新增一个email_clean字段VARCHAR(100) NOT NULL DEFAULT 逐步填充有效邮箱。阶段三离线清洗 约束灰度在从库上执行清洗-- 为 email_clean 填充数据示例用用户名拼接 UPDATE users SET email_clean CONCAT(username, example.com) WHERE email_clean AND username ! ; -- 创建唯一索引不影响主库 ALTER TABLE users ADD UNIQUE KEY uk_email_clean (email_clean);阶段四切流与最终约束当email_clean字段填充率 99.5% 时将应用读写切换到email_clean再在主库上执行最终ALTER TABLE users MODIFY COLUMN email VARCHAR(100) NOT NULL, ADD UNIQUE KEY uk_email(email);。警告绝对不要用UPDATE users SET email defaultexample.com WHERE email IS NULL OR email ;这种暴力填充。它会让所有空邮箱用户收到同一封验证码邮件是严重的安全漏洞。4. 约束失效的五大陷阱你以为加了其实没起作用加了约束不等于万事大吉。MySQL 5.7 的某些机制会让约束“静默失效”而你可能几个月都发现不了。以下是我在生产环境中亲手挖出的五个经典陷阱每个都附带验证方法和修复命令。4.1 陷阱一SQL 模式宽松NOT NULL形同虚设如前所述当sql_mode不含STRICT_TRANS_TABLES时MySQL 5.7 会对非法值进行“静默修正”。验证方法极其简单-- 创建测试表 CREATE TABLE test_null (id INT NOT NULL) ENGINEInnoDB; -- 插入空值非 NULL而是空字符串 INSERT INTO test_null VALUES (); -- 查询结果 SELECT * FROM test_null; -- 输出id0看到id0就证明约束失效了因为被转成了整数0而0是合法的INT值NOT NULL检查通过。修复命令-- 临时生效当前会话 SET SESSION sql_mode STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION; -- 永久生效需修改 my.cnf 并重启 -- sql_mode STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION注意STRICT_TRANS_TABLES仅对事务表InnoDB生效STRICT_ALL_TABLES对所有表生效但可能影响 MyISAM 表的兼容性推荐前者。4.2 陷阱二UNIQUE索引失效于前缀索引当字段很长如VARCHAR(500)时为节省空间开发者常建前缀索引UNIQUE KEY uk_title (title(100))。这会导致UNIQUE约束只检查前100个字符后面的内容被忽略。验证方法-- 创建带前缀唯一索引的表 CREATE TABLE articles ( id INT PRIMARY KEY, title VARCHAR(500), UNIQUE KEY uk_title (title(100)) ) ENGINEInnoDB; -- 插入两条前100字符相同、后面不同的标题 INSERT INTO articles VALUES (1, REPEAT(A, 100) v1); INSERT INTO articles VALUES (2, REPEAT(A, 100) v2); -- 成功约束未触发修复方案只有两个要么放弃前缀索引用全文索引FULLTEXT替代要么在应用层用哈希值如MD5(title)做唯一校验。4.3 陷阱三FOREIGN KEY被ENGINEMyISAM无视MyISAM 引擎完全不支持外键约束。如果建表时写了FOREIGN KEYMySQL 5.7 会静默忽略不报错也不生效。验证方法-- 创建 MyISAM 表并加外键 CREATE TABLE parent (id INT PRIMARY KEY) ENGINEMyISAM; CREATE TABLE child (pid INT, FOREIGN KEY (pid) REFERENCES parent(id)) ENGINEMyISAM; -- 查看建表语句 SHOW CREATE TABLE child\G -- 输出中看不到 FOREIGN KEY 定义修复命令ALTER TABLE child ENGINEInnoDB;但注意这会锁表需按前述“三步法”操作。4.4 陷阱四DEFAULT值在INSERT ... SELECT中不生效DEFAULT约束只在INSERT INTO ... VALUES (...)或INSERT INTO ... SET ...语句中生效。当用INSERT INTO ... SELECT ...从其他表导入数据时DEFAULT值不会自动填充字段会得到NULL如果允许或报错如果不允许。验证方法CREATE TABLE t1 (id INT PRIMARY KEY, name VARCHAR(10) DEFAULT unknown); CREATE TABLE t2 (id INT); INSERT INTO t2 VALUES (1); INSERT INTO t1 (id) SELECT id FROM t2; -- 报错ERROR 1364 (HY000): Field name doesnt have a default value修复方案在SELECT中显式写出DEFAULT值INSERT INTO t1 (id, name) SELECT id, unknown FROM t2;4.5 陷阱五CHAR类型的尾部空格被忽略导致UNIQUE误判CHAR类型会用空格填充到固定长度而 MySQL 在比较CHAR值时会忽略尾部空格。这会导致UNIQUE约束认为abc和abc 是同一个值。验证方法CREATE TABLE t_char (c CHAR(10) UNIQUE); INSERT INTO t_char VALUES (abc); INSERT INTO t_char VALUES (abc ); -- 成功但 abc 被存为 abc SELECT CONCAT(, c, ) FROM t_char; -- 输出abc 和 abc 修复方案一律用VARCHAR替代CHAR除非你明确需要定长存储如国家代码CHAR(2)。VARCHAR在比较时不会忽略尾部空格。总结这五个陷阱它们共同的特点是“不报错、不警告、静默失效”。唯一的防御手段是每次加完约束必须用非法数据手工测试一次。比如加了NOT NULL就试插一个空字符串加了UNIQUE就试插两条看似不同实则会被认为相同的值。5. 约束与性能的博弈什么时候该加什么时候该砍约束不是越多越好。在 MySQL 5.7 中每个约束都对应着存储开销、写入延迟和锁竞争。盲目堆砌约束可能让数据库从“数据守门员”变成“性能绊脚石”。我的判断原则是以业务 SLA 为锚点用压测数据说话而不是凭感觉决策。5.1 写入密集型场景牺牲部分约束保吞吐我们曾负责一个实时消息推送系统每秒要写入8万条推送记录到push_log表。原始设计中push_log有UNIQUE KEY uk_msg_id(msg_id)msg_id是业务生成的 UUID。上线后发现写入延迟从平均8ms飙升到230msSHOW ENGINE INNODB STATUS显示大量insert intention lock等待。根因分析UUID 是无序的每次插入都要在 BTree 索引中随机找位置导致页分裂频繁而UNIQUE约束要求每次插入前都得搜索整个索引树确认不重复IO 成倍增加。解决方案是“降级约束”移除uk_msg_id改用应用层幂等控制Redis 记录msg_id是否已处理为提升查询效率保留KEY idx_user_id(user_id)user_id是有序的BIGINT增加定时任务每天凌晨用pt-duplicate-key-checker工具扫描重复msg_id人工核查。效果写入延迟回落至11msP99 延迟从500ms降至45ms。业务方接受“极低概率重复推送”换取“99.99% 推送成功率”。关键指标当单表 QPS 3000且INSERT占比 70% 时谨慎添加UNIQUE约束优先考虑应用层幂等。5.2 读多写少型场景约束是性能加速器与之相反在报表类、BI 分析类表中约束往往是性能利器。比如一张sales_summary汇总表按date和region聚合PRIMARY KEY (date, region)不仅保证数据唯一更让SELECT * FROM sales_summary WHERE date 2023-01-01直接走聚簇索引避免全表扫描。我们曾将一个 2.3 亿行的销售明细表按月归档为汇总表并为主键(year_month, product_id)加PRIMARY KEY。同样的聚合查询响应时间从 18.7 秒降至 0.23 秒——因为PRIMARY KEY的聚簇索引让数据物理上按year_month顺序存储磁盘 IO 减少 95%。5.3 约束的“性价比”评估表帮你快速决策面对一个新字段如何判断是否值得加约束我用这张表做快速评估基于 MySQL 5.7 实测数据约束类型典型场景写入性能影响单行存储开销必要性评分1-5决策建议NOT NULL所有必填字段如user_id,amount 0.1ms0 字节★★★★★必加无例外PRIMARY KEY表的主键字段0.3-0.8ms取决于索引深度约 8-12 字节/行★★★★★必加且必须是BIGINT AUTO_INCREMENTUNIQUE业务强唯一字段如order_no,mobile1.2-3.5ms索引搜索插入约 10-20 字节/行★★★★☆高频写入表慎用低频表必加FOREIGN KEY跨表强引用如orders.user_id → users.id2.0-8.0ms全表扫描风险0 字节但锁开销大★★☆☆☆MySQL 5.7 中建议禁用用应用层保障DEFAULT时间戳、状态码等固定初值字段 0.05ms0 字节★★★★☆必加减少应用层赋值逻辑个人体会在 MySQL 5.7 的世界里“加约束”不是开发完成的句号而是性能调优的起点。每次加完约束我必做三件事用sysbench压测写入性能、用pt-index-usage分析索引使用率、用EXPLAIN FORMATJSON看查询执行计划。数据不会说谎感觉会骗人。6. 最后的实战检查清单上线前必须过一遍在你准备执行ALTER TABLE ADD CONSTRAINT之前请拿出这张清单逐项核对。它是我从三次严重线上事故中提炼出的“防呆指南”每一条都对应一个真实踩过的坑。[ ]字段空值检查SELECT COUNT(*) FROM table_name WHERE column_name IS NULL OR column_name OR TRIM(column_name) ;结果必须为 0否则NOT NULL或UNIQUE会失败。[ ]重复值检查SELECT column_name, COUNT(*) FROM table_name GROUP BY column_name HAVING COUNT(*) 1;结果必须为空否则UNIQUE添加失败。[ ]SQL 模式确认SELECT sql_mode;输出中必须包含STRICT_TRANS_TABLES否则NOT NULL约束可能被绕过。[ ]引擎确认SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA db_name AND TABLE_NAME table_name;必须是InnoDBMyISAM不支持外键和事务。[ ]索引长度检查如果加UNIQUE KEY到VARCHAR字段确认前缀长度不超过 767 字节utf8mb4下最多 191 字符否则报错ERROR 1071 (42000): Specified key was too long。[ ]外键引用表检查如果加FOREIGN KEY确认被引用表的字段有INDEX最好是PRIMARY KEY或UNIQUE KEY否则报错ERROR 1005 (HY000): Cant create table。[ ]备份确认mysqldump -h host -u user -p --no-data db_name table_name table_struct.sql确保结构备份成功。[ ]回滚语句准备写下对应的DROP语句如ALTER TABLE table_name DROP INDEX uk_column_name;放在脚本第一行随时可执行。[ ]低峰期窗口确认查看业务监控确认操作窗口内 QPS 500且无重要批处理任务运行。[ ]通知到位提前 2 小时邮件通知所有相关方开发、测试、运维、产品注明操作时间、影响范围、回滚方案。这张清单看起来繁琐但每一次跳过它都可能换来一次深夜的紧急会议。我见过最惨的一次是跳过了“空值检查”在一张 1.2 亿行的表上执行MODIFY COLUMN email VARCHAR(100) NOT NULL结果卡在 98% 进度锁表 37 分钟导致支付成功率

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑