资讯详情

资讯详情

MySQL建表与ALTER TABLE实战:从字段类型到存储过程的避坑指南

1. 建表一张表从零到一的完整骨架1.1 建表前必须想清楚的核心参数MySQL建表这件事看起来只要把字段名和类型写出来就行但实际开发中因为少写了一个参数或者选错了类型而产生线上事故的例子我见过太多了。建表前你一定要先想清三件事存储引擎、字符集、主键策略。存储引擎现在基本默认选InnoDB原因不用多说支持事务、支持行级锁、支持外键崩溃恢复也靠谱。MyISAM那种老引擎只适合做只读报表或者测试别在生产环境里边用InnoDB边配一个MyISAM的表后面数据一致性会出大问题。字符集我统一推荐utf8mb4对应排序规则一般用utf8mb4_general_ci就够如果你的业务涉及多种语言排序要求更高的精准度再上utf8mb4_0900_ai_ciMySQL 8.0默认。这里特别提醒建表时没指定字符集表就会继承库的字符集库继承实例的到后面字段出现乱码再来改代价翻倍。主键策略上我习惯用BIGINT UNSIGNED AUTO_INCREMENT再配合一个业务层面的order_no之类的唯一键做逻辑定位。为什么不直接用业务字段做主键因为业务字段太容易变了今天是手机号明天可能就得支持邮箱登录主键一旦变更整张表的二级索引全部重排。自增主键还有一个好处就是InnoDB的聚簇索引按主键顺序写入顺序插入的性能远高于随机插入数据页的碎片也更少。CREATE TABLE user_account ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_no VARCHAR(32) NOT NULL COMMENT 用户编号业务唯一标识, user_name VARCHAR(50) NOT NULL DEFAULT COMMENT 用户姓名, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态0-禁用1-正常, balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 账户余额单位元, last_login_at DATETIME DEFAULT NULL COMMENT 最近登录时间, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_user_no (user_no), KEY idx_status (status) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 用户账户表;这张表里我重点解释几个容易被忽略的细节。update_time用ON UPDATE CURRENT_TIMESTAMP每次行记录被修改时都会自动更新省掉你每次UPDATE都要手动带一个update_time NOW()的功夫。create_time用DEFAULT CURRENT_TIMESTAMP插入时自动带上不需要业务代码传时间。这两个字段是绝大多数业务表的标配谁建表漏了后面补代价都不小。1.2 字段类型选择的血泪经验字段类型这块真的是建表里最容易埋雷的地方。我见过有人用VARCHAR(255)存订单金额的也见过用FLOAT存钱的最后对账对不上发现是精度丢失只能写脚本全表修。这里几条硬性经验都是踩坑踩出来的金额、价格、费率这类数据一律用DECIMAL不要用FLOAT/DOUBLE。二进制浮点数在计算机里本身就是近似值10.1加上0.2你能得到10.300000000000001。DECIMAL(12,2)的意思是最多12位有效数字小数点后保留2位常规业务完全够用。整数类型按范围选TINYINT0-255或-128到127、SMALLINT65535、INT21亿多、BIGINT天文数字。别动不动全用BIGINT索引体积变大查询性能跟着受影响看起来省事实际上血亏。字符串能用VARCHAR不要用TEXT。TEXT字段没法给完整的默认值索引时必须要指定前缀长度排序对比也走临时表性能完全比不上等长的VARCHAR。数据库里VARCHAR(50)存的是可变长度不是50个字符固定占满。时间字段用DATETIME还是TIMESTAMP我的建议是直接用DATETIME。TIMESTAMP有2038年问题虽然看着遥远但你这张表是要用十年的别赌。另外存储空间从MySQL 5.6以后DATETIME也是变长存储了两者没差多少。状态类字段用TINYINT不要用VARCHAR(10)存“1”、“2”这种字符串。序列化和反序列化还要转类型索引效率也低。还有一点是关于字段名和注释的。字段名尽量统一风格全小写加下划线比如user_name、create_time不要一会儿驼峰一会儿下划线。注释必须写写清楚这个字段什么含义、单位是什么、枚举值代表什么。后面做数据字典、接口对接、新人接手全都靠注释活着。2. 新增字段ALTER TABLE的日常扩展2.1 基础语法与加字段的三种模式业务迭代最常规的操作就是给现有表加字段了。比如用户表要加一个“性别”字段或者订单表要加一个“优惠券ID”语法很固定ALTER TABLE user_account ADD COLUMN gender TINYINT NOT NULL DEFAULT 0 COMMENT 性别0-未知1-男2-女;这行SQL执行完字段默认加在表的最后位置。但如果你希望新字段加在指定字段后面比如放在user_name后面就需要用AFTERALTER TABLE user_account ADD COLUMN gender TINYINT NOT NULL DEFAULT 0 COMMENT 性别0-未知1-男2-女 AFTER user_name;如果想加在表的最前面用FIRST。不过我几乎不用FIRST因为实际业务里很少需要把某个字段放在第一个。特别注意一下FIRST和AFTER在8.0里有一个INSTANT算法的限制有些时候你在线变更会锁表后面讲生产操作章节我会专门展开。一次加多个字段不要一行行执行多次合并成一条ALTER TABLE更高效。多条ADD COLUMN放在同一个语句里MySQL只扫描一次表结构执行时间远小于分开执行ALTER TABLE user_account ADD COLUMN phone VARCHAR(20) NOT NULL DEFAULT COMMENT 手机号 AFTER user_name, ADD COLUMN email VARCHAR(100) NOT NULL DEFAULT COMMENT 邮箱 AFTER phone, ADD COLUMN user_level TINYINT NOT NULL DEFAULT 0 COMMENT 用户等级0-普通1-VIP AFTER status;这里还有一个经验在同一个ALTER语句里把多个变更合并时如果其中有一个操作不支持在线DDL比如某些版本下修改主键整个语句都会变成COPY TABLE其他本来可以秒加的字段也跟着遭殃。所以我会按操作的“危险度”分开执行同类安全的合并有风险的单拎出来。2.2 加字段时默认值的坑新手必看加字段时最麻烦的不是语法而是“这个新增字段该不该有默认值”。线上表一般都有大量存量数据加一个NOT NULL且没有默认值的字段会直接报错因为存量行的这个字段不知道填什么-- 这行如果表里有存量数据会直接报错 ALTER TABLE user_account ADD COLUMN gender TINYINT NOT NULL COMMENT 性别;正确做法是给默认值或者先加可空字段再填数据最后再改NOT NULL。但“给默认值”也不是随便给的这里分两种场景业务上这个字段本来就有一个明确的“初始状态”比如状态字段默认1直接NOT NULL DEFAULT 1完事。这个字段需要根据存量数据计算的比如“最近登录时间”此时应该先加一个NULL DEFAULT NULL的字段然后写UPDATE脚本从日志表回填最后再决定要不要改成NOT NULL。-- 第一步先加可空字段 ALTER TABLE user_account ADD COLUMN first_order_time DATETIME NULL COMMENT 首单时间; -- 第二步从订单表回填 UPDATE user_account u JOIN ( SELECT user_no, MIN(create_time) AS first_time FROM order_info GROUP BY user_no ) t ON u.user_no t.user_no SET u.first_order_time t.first_time; -- 第三步确认没有漏数据后再收紧约束 ALTER TABLE user_account MODIFY COLUMN first_order_time DATETIME NOT NULL DEFAULT 1970-01-01 00:00:00 COMMENT 首单时间;这里用1970-01-01做兜底默认值是为了避开NULL判断如果你业务上不关心这个值也可以保持NULL。我倾向于能不用NULL就不用NULL在索引、聚合函数、ORM映射层面都会带来额外的分支判断。2.3 大表加字段的锁表风险对千万行以上的大表执行ALTER TABLE ... ADD COLUMN很多人的做法是直接扔到生产库跑然后跑着跑着发现业务接口全部超时。根本原因在于MySQL 5.7及以下版本大部分加字段操作是COPY TABLE算法也就是要复制一份全表数据到新表期间全程锁写甚至锁读。数据量大时跑一个多小时很正常这一个多小时业务写入全部卡死。MySQL 8.0里的INSTANT算法支持只修改元数据秒级完成但它只适用于把字段加到表的末尾。如果你的字段要加在中间某列后面可能会退化成INPLACE或COPY。所以生产环境加字段我总结了一个操作顺序先查一下表的数据量SELECT COUNT(*) FROM 表名引擎会自动走主键大表也会比较慢但至少知道量级。看information_schema.INNODB_TABLES或直接SHOW TABLE STATUS确认表大概多大。如果表超过百万行且MySQL为5.7尽量用在线变更方案新建一张临时表拷贝数据、业务侧双写、切换表名或者引入业界成熟的在线DDL工具这类工具利用触发器或binlog把增量变更同步到新表实现业务无感。工具虽好我建议先在测试环境完整演练一遍。变更前设置锁等待时间避免直接把线上库锁死至少让异常情况可以自动终止。提示如果你在业务低峰期操作也要盯着SHOW PROCESSLIST看到长时间卡住的写事务赶紧评估是否回滚或终止。线上库宁可慢一点也不能让主库全部堵死。3. 变更字段MODIFY与CHANGE的差异及实战3.1 MODIFY COLUMN改类型、改默认值、改注释变更字段最常用的是MODIFY COLUMN。它的语义是“修改列定义”不改变列名。典型场景包括VARCHAR(50)不够用了要扩到VARCHAR(100)默认值从0改成-1补一条注释等。-- 扩字段长度 ALTER TABLE user_account MODIFY COLUMN user_name VARCHAR(100) NOT NULL DEFAULT COMMENT 用户姓名; -- 改默认值 ALTER TABLE user_account MODIFY COLUMN user_level TINYINT NOT NULL DEFAULT 1 COMMENT 用户等级0-普通1-VIP; -- 修改字段类型设置新注释 ALTER TABLE order_info MODIFY COLUMN total_amount DECIMAL(12,4) NOT NULL DEFAULT 0.0000 COMMENT 订单总金额元保留4位小数;这里有个重要细节MODIFY COLUMN必须把列定义的完整属性重新写一遍包括类型、是否为空、默认值、注释。如果你只写MODIFY COLUMN user_name VARCHAR(100)那么这个字段原本的NOT NULL和注释都会被丢掉变成默认的允许NULL。很多新手在这里栽跟头改完发现字段的注释没了甚至非空约束被解除数据写入一堆脏数据。所以我在执行MODIFY之前一定会先查一下当前的完整列定义SHOW CREATE TABLE user_account;看清楚原列定义把要保留的属性原样带回新语句里。这一个习惯至少帮我避免了十几次线上“莫名其妙丢注释丢约束”的事故。3.2 CHANGE COLUMN改列名注意别踩坑CHANGE COLUMN和MODIFY COLUMN最大的区别是它可以同时修改列名。语法上注意旧列名和新列名要写两遍ALTER TABLE user_account CHANGE COLUMN user_name nick_name VARCHAR(50) NOT NULL DEFAULT COMMENT 昵称;user_name是旧列名nick_name是新列名后面的类型定义是新列的完整定义。如果你只改列名、不改变类型也要把类型原样抄一遍。不要小看这个列名变更的连锁反应。一个字段被改名后所有涉及这个字段的存储过程、视图、ORM实体类、SQL查询、报表脚本全部要同步检查。我的经验是上线前用文本工具或IDE全局搜索旧字段名一个一个排查引用点变更脚本里加上“禁止查询”的窗口保证没有漏网之鱼。另一个坑是在MySQL 5.7下CHANGE一个被索引覆盖的字段索引会自动跟着改名吗二级索引会但如果是组合索引的一部分索引的列名定义也会同步更新但索引名字不会变。你要是没搞清楚后面删索引可能命名对不上。3.3 变更字段类型时的兼容性与数据安全修改字段类型不是单纯把INT改成BIGINT那么简单MySQL对数据类型的转换有严格的兼容规则。比如VARCHAR(50)扩到VARCHAR(100)安全一般不用锁数据复制。VARCHAR改成TEXTMySQL 5.7会COPY全表并且默认值会失效。这个操作谨慎执行。DECIMAL(10,2)改成DECIMAL(12,2)安全但同样可能COPY。VARCHAR直接改成INT如果存量数据里有非数字字符执行时直接报错而且错误发生在复制数据中间可能留下一张半成品表。DATETIME改成TIMESTAMP范围变小超过2038年的数据直接转换失败。所有类型变更只要是从“小类型”向“大类型”转兼容性都还好反过来从“大类型”收窄到“小类型”一定要先跑数据预检。我通常这样检查-- 检查是否有超过新类型范围的值 SELECT COUNT(*) FROM order_info WHERE total_amount -999999.99 OR total_amount 999999.99; -- 检查能否安全转INT SELECT COUNT(*) FROM user_temp WHERE user_no REGEXP [^0-9] LIMIT 10;自定义字段变更之前先备份表。备份不一定要物理备份CREATE TABLE 表名_bak_20250101 AS SELECT * FROM 表名就够用虽然不完美但能快速回滚。线上重要表我更推荐用逻辑备份工具或者直接走主从切换验证不管怎样有可回滚的备份再执行变更是绝对不能省的步骤。4. 存储过程把重复SQL打包成可复用逻辑4.1 存储过程的基本结构与参数模式存储过程在MySQL里的使用率比SQL Server和Oracle低很多因为很多团队嫌维护麻烦全让业务代码去处理。但有些场景它就是无法替代的你需要一次性完成多步操作且每一步之间有强依赖或者你需要处理大量历史数据在存储过程中做循环。它的核心优势是预编译、减少网络交互、事务内执行。一个标准的存储过程骨架是这样的DELIMITER $$ DROP PROCEDURE IF EXISTS sp_update_user_level$$ CREATE PROCEDURE sp_update_user_level( IN p_user_no VARCHAR(32), IN p_level TINYINT, OUT p_effect_rows INT ) COMMENT 根据用户编号更新用户等级返回影响行数 BEGIN DECLARE v_count INT DEFAULT 0; -- 参数校验 IF p_user_no IS NULL OR p_user_no THEN SIGNAL SQLSTATE 45000 SET MESSAGE_TEXT 参数错误用户编号不能为空; END IF; UPDATE user_account SET user_level p_level, update_time NOW() WHERE user_no p_user_no; SET p_effect_rows ROW_COUNT(); -- 记录一条操作日志 INSERT INTO op_log(biz_type, biz_no, op_content, create_time) VALUES (UPDATE_USER_LEVEL, p_user_no, CONCAT(更新用户等级为, p_level), NOW()); SELECT v_count; END$$ DELIMITER ;注意几个重点。DELIMITER $$是把结束符临时改成$$因为过程体里的分号会被MySQL当成语句结束不改的话你在命令行里写不完整。IN是入参OUT是出参INOUT是既能传进来又能在内部修改再传出去。参数校验那里用了SIGNAL SQLSTATE 45000这是主动抛出异常调用方可以直接捕获错误信息。调用方式如下-- 用SET接收返回值 CALL sp_update_user_level(U10001, 2, effect_rows); SELECT effect_rows;如果你不需要返回值直接CALL sp_update_user_level(U10001, 2, t)就行。4.2 实战写一个批量清洗历史数据的存储过程存储过程最典型的应用场景之一是“批量刷数据”。比如公司业务出了一个新规则所有2023年之前注册且从未下过单的用户等级统一降为普通用户并发送一张补偿优惠券。这个操作涉及三张表用户表、订单表、优惠券表逐行处理还要记录日志。用一条SQL做也可以但过程中要判断状态、防止重复发券、统计处理数量这时存储过程的优势就体现出来了。DELIMITER $$ DROP PROCEDURE IF EXISTS sp_batch_downgrade_users$$ CREATE PROCEDURE sp_batch_downgrade_users( IN p_batch_size INT, IN p_start_time DATETIME, IN p_end_time DATETIME ) COMMENT 批量降级2023年前注册且无订单用户发补偿券并记录日志 BEGIN DECLARE v_total INT DEFAULT 0; DECLARE v_success INT DEFAULT 0; DECLARE v_user_no VARCHAR(32); DECLARE v_done INT DEFAULT 0; -- 定义游标查询符合条件的用户 DECLARE cur_user CURSOR FOR SELECT u.user_no FROM user_account u WHERE u.status 1 AND u.create_time BETWEEN p_start_time AND p_end_time AND NOT EXISTS ( SELECT 1 FROM order_info o WHERE o.user_no u.user_no ) LIMIT p_batch_size; DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done 1; OPEN cur_user; REPEAT FETCH cur_user INTO v_user_no; IF v_done 0 THEN -- 降级用户 UPDATE user_account SET user_level 0, update_time NOW() WHERE user_no v_user_no; -- 发补偿券如果没发过才发 INSERT INTO coupon_issue_log(user_no, coupon_type, issue_time, remark) SELECT v_user_no, COMPENSATE, NOW(), 自动降级补偿 WHERE NOT EXISTS ( SELECT 1 FROM coupon_issue_log WHERE user_no v_user_no AND coupon_type COMPENSATE ); SET v_success v_success 1; END IF; UNTIL v_done END REPEAT; CLOSE cur_user; SELECT CONCAT(总处理用户数, v_success); END$$ DELIMITER ;这个存储过程里用到了游标循环。游标在这里一次取一批数据每次循环先降级再发券发券前做了NOT EXISTS去重判断。要处理几万行历史数据脚本根本跑不满存储过程一把梭就完事了。强调一下存储过程内部默认使用当前事务隔离级别整个过程在一个事务里执行。如果你的p_batch_size设得很大这个过程会持有大量行锁影响其他会话对表的操作。我一般把批次控制在1000以内跑完一批提交一次业务侧几乎无感。4.3 存储过程的坑与维护建议存储过程很好用但我平时不怎么写。原因很简单调试困难版本管理困难几点一线全是坑我挑常见的几个说权限不统一存储过程默认的SQL SECURITY是DEFINER也就是只有定义者有权限执行时别的用户调用才能生效。如果定义者账号被删除或者权限变更所有调用方直接报错。建议显式写成SQL SECURITY INVOKER谁调用按谁的权限执行逻辑更透明。参数校验要做好不校验入参直接去UPDATE/DELETE存储过程里一条误操作影响范围就是全表。我强烈建议所有涉及更新和删除的存储过程都加上入参校验校验不过直接SIGNAL抛异常。备份脚本和版本管理存储过程的代码要在代码仓库里留一份版本用文件命名区分日期别只存在数据库里。数据库被重建或者切换环境时没有脚本就是灾难。尽量少在存储过程中写复杂业务超过50行的存储过程建议拆分否则后面接手的人想死的心都有。能用一条SQL搞定的业务不要绕弯写游标逐行处理数据集操作和逐行操作的性能差距是数量级的。ROW_COUNT()返回的是上一条语句影响的行数如果你在UPDATE之后又执行了其他语句再取ROW_COUNT()就不是你要的值了。要保存到变量立刻消费。提示在MySQL 8.0里存储过程默认是二进制日志记录为STATEMENT模式还是ROW模式会影响主从复制的一致性。如果你用基于语句的复制存储过程里尽量别用NOW()这种不确定性函数否则从库重放可能会产生和主库不一样的执行结果。这块稍微注意一下就行真遇到主从不一致再排查就麻烦了。5. 注释表注释、字段注释的规范管理5.1 COMMENT的添加、修改与查看很多开发写建表SQL时最懒的就是注释觉得这玩意儿可有可无。两周之后自己看着字段都发懵更别说别人接手。注释不是可有可无它是数据字典的一部分是表结构可读性的底线。建表时注释写在哪表注释写在表定义的最外层字段注释写在各字段定义后面CREATE TABLE user_account ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, user_name VARCHAR(50) NOT NULL DEFAULT COMMENT 用户姓名 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户账户表;已经存在的表修改表注释用ALTER TABLE user_account COMMENT 用户账户表含VIP等级信息;修改字段注释必须走MODIFY COLUMN而且和前面说的一样要把字段的完整定义带上ALTER TABLE user_account MODIFY COLUMN user_level TINYINT NOT NULL DEFAULT 0 COMMENT 用户等级0-普通1-VIP2-至尊;查看表结构里的注释最直接的两个方法-- 看完整建表语句注释会显示在每行里 SHOW CREATE TABLE user_account; -- 查系统库拿到字段级别的注释和类型适合做数据字典 SELECT COLUMN_NAME AS 字段名, COLUMN_TYPE AS 字段类型, IS_NULLABLE AS 是否可为空, COLUMN_DEFAULT AS 默认值, COLUMN_COMMENT AS 字段注释 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA your_database_name AND TABLE_NAME user_account ORDER BY ORDINAL_POSITION;第二个查询直接导出成Excel就是一份基础数据字典。我工作里经常拿这条SQL出来配合文档工具生成接口文档的表字段说明省掉大量手工维护文档的时间。5.2 注释该怎么写才有价值注释不是随便写一句话就叫注释我见过最没用的注释就是“用户编号”“创建时间”这种字段名叫user_no注释写“用户编号”等于没写。真正有用的注释应该能让读的人不看代码就知道字段的规则和场景。举几个参考状态枚举类字段状态0-禁用1-正常2-锁定金额类字段账户余额单位元保留2位小数标识类字段用户编号业务唯一键格式U年月日序列号时间类字段创建时间UTC时间展示时转换为北京时间注释里也可以带上维护责任人或者业务归属方比如“本字段由交易中台维护”这样出现疑问时大家知道该找谁。但这里要注意不要把人名写成核心财产信息写团队、系统名就好。另一个建议是枚举值特别多的时候注释里写不下就把枚举定义放在表注释里或者设计一张字典表字段注释里写“枚举见dict_codexxx”。比如状态字段可能有十几种状态写成注释会又臭又长。5.3 从注释到自动化数据字典如果你们团队还没有数据字典系统我建议先靠information_schema注释把数据字典跑起来。在很多企业里表结构和字段注释是自动生成接口文档、前端低代码页面的基础数据源。只要你的注释写得规范后续做数据治理、合规审计、自动生成Python/Java的ORM模型都能直接受益。拿代码生成举例很多工具根据COLUMN_COMMENT生成实体类的字段说明注释根据COLUMN_TYPE生成Java的Integer/String/BigDecimal类型映射。你的注释写得不清楚生成的代码注释也就全是废话。字段命名规范和注释规范是配套的两端都做好后面整个开发效率能提升不少。6. 生产环境的实操避坑清单6.1 执行DDL前必须养成的检查习惯线上执行任何DDL语句我建议你按这个顺序过一遍能拦住90%的事故查表大小和行数确认变更影响面有多大是否适合直接执行。查变更时段业务高峰期绝对不做大表结构变更凌晨两三点通常是最佳窗口。就算有在线DDL也要避开业务波峰。确认锁等待时间执行前设置SET SESSION innodb_lock_wait_timeout 5;至少不能默认等50秒否则别的会话锁着你的DDL你傻等完事还阻塞后面的队列。先跑EXPLAIN或预检SQL比如字段类型变更是否会导致数据丢失先跑前面的预检SQL。备份先行重要表至少导出一份逻辑备份文件保存到安全目录。脚本评审DDL脚本提交到代码仓库走评审流程别直接在线上敲命令。这一步是防止“手一抖忘加WHERE条件”的最有效手段。6.2 常见报错与排查实录我整理了平时被问得最多的几个MySQL DDL报错以及对应的排查思路报错信息常见原因解决方案Duplicate column name xxx表里已经有这个字段了先SHOW COLUMNS FROM 表名确认现状幂等脚本里加存在性判断再执行SQL 1067 - Invalid default value for xxx默认值不符合字段类型规则比如DATETIME用0000-00-00MySQL 8.0默认禁用零日期改成1970-01-01 00:00:00或NULLRow size too large表字段过多或某字段长度太长单行数据超过65535字节限制MySQL每行字节数有限TEXT/BLOB只占一部分把太长的VARCHAR拆到子表或改为TEXTLock wait timeout exceededDDL等锁超时别的会话正持锁查SHOW PROCESSLIST找锁源确认谁占了锁再决定等待或KILLThe table xxx is full磁盘空间不足先清理binlog或扩容磁盘别贸然重试Access denied ... SUPER privilege账号权限不够执行DDL或存储过程确认账号是否有ALTER和CREATE ROUTINE权限排查这类问题的通用思路是先看SHOW PROCESSLIST和information_schema.INNODB_TRX了解当前数据库的锁情况和事务状态。大多数锁超时都是有一个事务开着没提交一直占着行锁或表锁把那个事务处理好DDL自然就能跑了。我印象最深的一次事故是执行一个存储过程批量更新时忘了一处字段没加索引结果几万行的表跑了几十分钟还把所有并发请求全堵在锁等待上。那次之后我养成了一个习惯任何批量UPDATE/DELETE的语句先看WHERE条件有没有命中索引没索引就先把索引建好再跑别头铁硬干。6.3 常用SQL速查表直接保存收藏最后我把这个主题相关的核心SQL整理成一个速查表基本覆盖标题里所有操作你可以直接复制修改使用-- 建表 CREATE TABLE table_name ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, field1 VARCHAR(50) NOT NULL DEFAULT COMMENT 字段1, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT表注释; -- 新增字段多个字段合并执行 ALTER TABLE table_name ADD COLUMN new_field1 VARCHAR(20) NOT NULL DEFAULT COMMENT 新字段1 AFTER field1, ADD COLUMN new_field2 INT NOT NULL DEFAULT 0 COMMENT 新字段2 AFTER new_field1; -- 修改字段类型/默认值/注释MODIFY不修改列名 ALTER TABLE table_name MODIFY COLUMN field1 VARCHAR(100) NOT NULL DEFAULT default COMMENT 字段1新注释; -- 修改列名和定义CHANGE ALTER TABLE table_name CHANGE COLUMN old_field new_field VARCHAR(50) NOT NULL DEFAULT COMMENT 新字段注释; -- 修改表注释 ALTER TABLE table_name COMMENT 新表注释; -- 查看表结构 SHOW CREATE TABLE table_name; -- 查看字段注释information_schema查询 SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA your_db AND TABLE_NAME table_name; -- 创建存储过程标准模板 DELIMITER $$ DROP PROCEDURE IF EXISTS sp_example$$ CREATE PROCEDURE sp_example(IN p_param INT) COMMENT 示例存储过程 BEGIN SELECT * FROM table_name WHERE field1 p_param; END$$ DELIMITER ; -- 调用存储过程 CALL sp_example(100);这些SQL我都会在测试环境完整演练一遍再拿到生产。不是不信任自己而是环境差异真的能坑人——字符集、SQL模式、版本差异任何一点小偏差都可能让一模一样的两条SQL产生完全不同的执行计划。你在MySQL 5.7上跑得飞起的语句到了MySQL 8.0可能要微调。养成测试环境先行、线上留备份的习惯这些操作对任何人来说都是基本功中的基本功。最后分享一个小建议表结构变更这块别靠脑子记靠流程管。把DDL变成脚本入库带上版本号和日期变更记录写清楚原因后面任何人回溯都能看懂当时为什么这么改。我就是靠这个习惯在无数次“这张表谁改的”的追问里全身而退的。数据库这行细心和规范永远比炫技重要。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →