拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL库表操作实战:字符集、字段类型与DDL锁表避坑指南

MySQL库表操作实战:字符集、字段类型与DDL锁表避坑指南 1. 为什么库和表的操作值得单独拿出来说我见过不少刚接触 MySQL 的人装好数据库、用 Navicat 或命令行连上之后第一件事就是建个库、建个表往里填几条数据。这些操作看起来简单到不需要思考但恰恰是在这些“基础操作”里藏着大量后续才会爆发的坑字符集和排序规则选错导致乱码、字段类型选错导致性能灾难、随手 DROP 之后才发现没有备份、ALTER TABLE 把线上表锁了十几分钟……所以我想把库和表的操作从头到尾捋一遍。这篇文章不是照着官方文档念命令而是站在实操的角度把每一步为什么要这么做讲清楚把那些文档里不会写、只有踩过坑才知道的细节补上。适合刚学 MySQL 的入门者也适合已经写了一段时间 SQL、但没系统梳理过库表设计逻辑的人。先给个总体印象库和表的操作本质上是在回答三个问题——数据放在哪、数据长什么样、数据怎么被高效地查出来。建库解决“放在哪”建表和改表解决“长什么样”索引和约束解决“怎么查得快、查得准”。理解了这层关系后面所有命令都有了落点。2. 建库背后的字符集与排序规则选择2.1 库的创建语法不是只有 CREATE DATABASECREATE DATABASE [IF NOT EXISTS] database_name [CHARACTER SET charset_name] [COLLATE collation_name];大多数人在入门时只背了CREATE DATABASE 库名;这种最短写法中间的IF NOT EXISTS、字符集、排序规则统统没管全靠数据库默认值。开发环境这么干问题不大但到了生产环境库的字符集和排序规则是决定全表默认行为的地基后期几乎不可能整体更换。IF NOT EXISTS的作用是幂等当同名库已存在时MySQL 会抛出一个 warning 而不是 error这对写初始化脚本、自动化部署脚本非常友好可以放心地把建库语句放进 CI/CD 流程里反复执行。2.2 字符集选 utf8mb4 还是 utf8这是个老生常谈却依然有人踩的坑。MySQL 里的utf8实际上不是真正的全量 UTF-8它最多支持 3 字节的字符存不了 emoji 和生僻汉字真正的 4 字节 UTF-8 编码在 MySQL 里叫utf8mb4。凡是涉及用户输入内容昵称、评论、签名的业务表一律用utf8mb4。从 MySQL 8.0 开始默认字符集就是utf8mb4默认排序规则是utf8mb4_0900_ai_ci。如果你用的是 8.0 版本很多烦恼其实已经帮你解决了。但如果是 5.7 或更老的版本默认字符集还是latin1不显式指定的话中文写入就会变成问号——这是大量乱码问题的根源。2.3 排序规则里的 ai、ci、as、cs 是什么意思排序规则Collation决定了字符串比较和排序的规则。拆开讲_aiaccent insensitive不区分重音。比如 MySQL 8.0 默认的utf8mb4_0900_ai_ci会把café和cafe视为相等。_cicase insensitive不区分大小写。这意味着WHERE name abc会同时匹配到ABC。_asaccent sensitive区分重音。_cscase sensitive区分大小写。实际开发里最常用的组合是utf8mb4_unicode_ci5.7 时代通用选择或 8.0 默认的utf8mb4_0900_ai_ci。如果需要精确区分大小写比如存验证码、账号可以在建表时对特定列单独指定utf8mb4_bin或utf8mb4_0900_as_cs。CREATE DATABASE user_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;为什么我习惯显式写 COLLATE因为不同版本的 MySQL 默认排序规则不一样同一个utf8mb45.7 是general_ci8.0 是0900_ai_ci。如果建库时全不指定跨版本迁移或主从复制时可能出现排序规则不一致的问题导致索引无法复用或复制报错。2.4 库操作里容易忽略的注意事项用SHOW CREATE DATABASE user_db;可以查看这个库实际的字符集和排序规则配置这是排查乱码问题的第一检查项。改库的字符集用ALTER DATABASE user_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意这个操作只会修改库的默认属性不会自动转换库内已有表的字符集。每张表的字符集是在建表时单独确定的表字符集会覆盖库的默认设置。所以“改了库的字符集但表还是乱码”是完全可能的情况需要逐表去改。下面会讲到ALTER TABLE ... CONVERT TO CHARACTER SET的用法。删除数据库用DROP DATABASE user_db;。这个命令没有确认环节执行即永久删除连回收站都没有。我的习惯是任何 DROP 操作前先执行SHOW DATABASES;确认一遍名字或者干脆在脚本里用变量拼接库名降低手滑的概率。3. 建表时的字段类型选择决定后期的性能上限3.1 建表语法中隐藏的能力建表的基础语法是CREATE TABLE [IF NOT EXISTS] table_name ( column1 datatype [NOT NULL] [DEFAULT value] [AUTO_INCREMENT] [COMMENT 说明], column2 datatype [NOT NULL] [DEFAULT value], ... PRIMARY KEY (column1), KEY index_name (column2), CONSTRAINT fk_name FOREIGN KEY (column3) REFERENCES other_table(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT表说明;很多人把建表当成“把字段名和类型写出来”忽略了几个关键点注释COMMENT、默认值、字符集、引擎、索引设置。这些字段在建表时定好远比后期 ALTER 修改省事得多。3.2 数值、字符串、日期三大类字段的选取逻辑数值字段要注意显示宽度和实际范围的区别。INT(11)里的11只是显示宽度配合ZEROFILL才有实际意义真正的存储范围是由INT本身决定的占 4 字节范围是 -2147483648 到 2147483647。如果存用户 ID、订单号这类可能超过这个范围的数值要提前用BIGINT8 字节。这是个典型的“看着差不多、后期爆了才后悔”的字段类型选择问题。字符串字段最核心的选择是CHAR和VARCHARCHAR(n)是定长字符串长度固定存储时末尾空格会被去掉适合存储固定长度的数据如手机号历史上是 11 位、身份证号、MD5 摘要等。定长的好处是读取速度快没有长度计算开销。VARCHAR(n)是变长字符串用 1-2 个字节额外记录长度适合大多数业务字段如用户名、地址、备注。VARCHAR和TEXT的边界也是新手容易模糊的地方VARCHAR最大 65535 字节但这个值受行总大小限制可以设置默认值TEXT不能有默认值且可能会使用外部存储取决于行大小。实际开发中超过 2000 字符的文本内容直接用TEXT类型更省心但要注意TEXT无法直接加默认值写入时必须保证有值。日期字段的选择上DATETIME和TIMESTAMP是最常用的两个DATETIME占 8 字节范围 1000-01-01 到 9999-12-31不受时区影响。TIMESTAMP占 4 字节范围 1970-01-01 到 2038-01-19会自动转换为当前时区的时间显示。我的选择逻辑记录创建时间、更新时间、业务发生时间一律用DATETIME省心且范围大只有需要跟踪时区变化比如全球化应用或对存储空间敏感的场景才考虑TIMESTAMP。配合DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP可以让数据库自动维护创建和更新时间避免每次写入都在业务代码里手动拼时间。CREATE TABLE user_info ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 用户名, email VARCHAR(128) NOT NULL COMMENT 邮箱, bio VARCHAR(500) DEFAULT 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_email (email) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT用户信息表;3.3 布尔值到底用什么类型MySQL 没有真正的BOOLEAN类型BOOL、BOOLEAN都是TINYINT(1)的别名。存进去的TRUE会变成 1FALSE变成 0。这意味着你可以用WHERE status 1来查询也可以用WHERE status TRUE但后者在框架和 SQL 解析器中可能被转换成 1看着更语义化实际没区别。不过真实项目里我更推荐用TINYINT存“状态”而非“布尔”0、1、2、3 分别代表不同业务状态以后加状态不用改表结构只改枚举含义就行。这个习惯帮我避免了很多次“需求要加一个状态就得 ALTER TABLE”的尴尬。3.4 自增主键的陷阱与替代方案自增主键是绝大多数业务表的选择写入性能好、索引叶子节点顺序增长、缓存命中率高。但有几个场景需要小心分布式环境下多个实例同时生成 ID自增主键会冲突。这时要用雪花算法或专门的 ID 生成服务。数据迁移、合并表时自增主键可能撞车需要重新规划起始值。删除大量数据后自增 ID 不会回退。如果业务上对外暴露了 ID比如订单号里的日期序号删除后新数据的 ID 会跳号看起来像“丢单”。替代方案是使用BIGINT UNSIGNED配 UUID 或雪花 ID。建表语法上写成id BIGINT UNSIGNED NOT NULL去掉AUTO_INCREMENT即可。要注意UUID 作为主键在 InnoDB 里会产生严重的页分裂问题因为 UUID 是无序的。如果必须用 UUID建议存去掉连字符的 16 字节二进制格式BINARY(16)或者用有序 UUID比如基于时间戳的 UUID v7。4. 修改表结构时的锁表风险与控制策略4.1 ALTER TABLE 的常见姿势日常开发里需求变更几乎必然伴随着表结构变化。常见的操作有-- 添加字段 ALTER TABLE user_info ADD COLUMN nickname VARCHAR(50) NOT NULL DEFAULT COMMENT 昵称 AFTER username; -- 修改字段类型 ALTER TABLE user_info MODIFY COLUMN bio TEXT COMMENT 个人简介; -- 重命名字段 ALTER TABLE user_info CHANGE COLUMN bio introduction VARCHAR(500) DEFAULT COMMENT 个人简介; -- 删除字段 ALTER TABLE user_info DROP COLUMN introduction; -- 添加索引 ALTER TABLE user_info ADD INDEX idx_username (username); -- 删除索引 ALTER TABLE user_info DROP INDEX idx_username;ADD COLUMN ... AFTER可以控制新字段的位置不写AFTER默认加到最后一列。MODIFY COLUMN和CHANGE COLUMN的区别是CHANGE可以同时改字段名和字段定义MODIFY只能改定义不能改名字。要注意的是CHANGE COLUMN即使不改名字也必须把旧名字写一遍新名字容易写成CHANGE COLUMN bio bio TEXT忘了改名字但语法还在。4.2 大表 DDL 的锁表原理这是整篇里最值得记住的内容。MySQL 8.0 之前ALTER TABLE最常见的执行方式是COPY创建新表、把旧表数据复制过去、删旧表、改新表名。这个过程中旧表会被加上 MDL 锁期间所有读写都被阻塞。表越大阻塞时间越长。MySQL 5.6 引入了INPLACE算法部分 DDL 操作可以原地修改而不用复制全表配合ALGORITHM和LOCK参数可以精确控制行为ALTER TABLE user_info ADD COLUMN nickname VARCHAR(50) DEFAULT , ALGORITHMINPLACE, LOCKNONE;ALGORITHMINPLACE尽量原地修改不复制全表数据。ALGORITHMCOPY强制使用复制方式一般不推荐。LOCKNONE允许 DML 并发执行即不锁表LOCKSHARED只允许读LOCKEXCLUSIVE禁止读写。这个参数组合不是想用就能用。添加索引、添加字段部分场景在 InnoDB 下可以做到INPLACELOCKNONE但修改字段类型、修改字符集这些操作一般需要重建表LOCKNONE会被忽略MySQL 会自动升级为LOCKSHARED或LOCKEXCLUSIVE甚至直接选择COPY算法。实操建议在测试环境执行要变更的 DDL 前先跑一遍EXPLAIN不能预测 DDL 行为但 MySQL 8.0 可以通过ALGORITHM和LOCK参数验证可行性——如果执行的语句支持在线变更MySQL 会正常返回如果不支持会直接报错拒绝执行。利用这一点可以先用LOCKNONE试运行报错就说明这个操作没法在线完成需要安排在低峰期进行。4.3 实际生产中的 DDL 策略我在生产环境执行大表 DDL流程一般是这样先在测试库跑一遍同样的 DDL观察耗时和表体积。确认目标表是否真的需要立即变更能否通过新建表 切换读写的方式低风险上线。业务低峰期执行比如凌晨 2 点到 5 点。执行前记录SHOW TABLE STATUS LIKE table_name;的行数和数据长度执行后再对比。设置lock_wait_timeout防止 MDL 锁等待导致会话卡死。对亿级数据的表我还有个习惯用pt-online-schema-changePercona Toolkit 里的工具来做在线表结构变更。它的原理是创建一个影子表通过触发器同步数据变更最后原子切换表名。整个过程不阻塞读写比原生 DDL 稳得多。虽然配置起来稍微麻烦但在关键业务表上值得付出这个成本。另一个常用技巧如果需要给大表加索引可以考虑先创建同结构的表、在新表上建好索引、再通过RENAME TABLE交换表名。但这个方案要处理数据一致性实际应用中更多是用pt-osc一类的工具替代。5. 索引与约束让查询快且准的设计核心5.1 主键、唯一索引、普通索引、联合索引索引是表操作里最容易“建了没感觉、不建出大事”的部分。先说最基础的分类主键索引每个表只能有一个且不能为 NULL。它在 InnoDB 里就是聚簇索引决定了数据的物理存储顺序。唯一索引字段值不能重复但允许 NULL。常用于邮箱、手机号、业务单号等天然唯一的字段。普通索引只是加速查询不限制唯一性。联合索引多个字段组合成一个索引遵循“最左前缀”原则。关于最左前缀原则举个具体的例子ALTER TABLE order_info ADD INDEX idx_user_status (user_id, status, created_at);这个联合索引能高效匹配的查询条件包括user_id单独查、user_id status、user_id status created_at全匹配。但如果查询只带status或只带created_at这个索引就帮不上忙了。设计联合索引的关键是字段顺序把等值查询的字段放前面范围查询的字段放后面。比如此例中user_id是等值条件status也是等值条件created_at是范围排序这样的顺序是最优的。如果把created_at放到最前面user_id在后面的顺序下WHERE status 1查询还是用不上索引。5.2 辅助索引的“回表”问题热搜词里出现了“辅助索引如何避免回表”这里一并讲透。InnoDB 的普通索引辅助索引叶子节点存的是索引列的值 主键值。当查询的字段不在辅助索引里时需要通过主键再去聚簇索引里取整行数据这个过程叫“回表”。回表本身不慢一行数据一次主键查找成本很低。但当查询命中几万行时几万次随机主键查找就会明显拖慢查询。解决方案是覆盖索引让辅助索引包含查询需要的所有字段。-- 常见业务查询 SELECT username, status FROM user_info WHERE status 1; -- 如果只建 idx_status(status)每次查询都要回表取 username。 -- 改成联合索引 ALTER TABLE user_info ADD INDEX idx_status_username (status, username);此时查询所需的username和status都在索引里不需要回表这个索引就“覆盖”了查询。这就是避免回表的核心思路。但要注意索引不是越多越好覆盖索引也会增加写入开销和存储空间需要取舍。5.3 索引的隐式失效场景索引建了查询还是慢最常见的几个原因对索引字段使用函数WHERE DATE(created_at) 2025-01-01会让索引失效。正确写法是WHERE created_at 2025-01-01 AND created_at 2025-01-02。隐式类型转换WHERE phone 13812345678字段是VARCHAR传入的是数值MySQL 会把字段转成数值比较导致索引失效。正确写法是传字符串13812345678。前导模糊查询LIKE %abc%用不了索引LIKE abc%可以。OR连接非索引列WHERE username abc OR status 1中只要有一个条件没索引整个查询可能不走索引。联合索引没满足最左前缀。排查索引是否生效最直接的办法是EXPLAIN SELECT ...看type列const、ref、range都是比较好的访问类型ALL说明是全表扫描index说明扫了全索引但也不一定高效。5.4 外键约束用还是不用外键约束在 MySQL 的 InnoDB 引擎下是支持的但互联网行业的真实场景里用得越来越少。原因是外键约束会让每次写入都去检查关联表在高并发写入时形成不必要的性能瓶颈而且分布式架构下分库分表之后外键直接失效。我的习惯是业务层面保证关联数据的完整性数据库层面不建物理外键但必须建普通索引。这样做既保留了查询效率又避免了外键带来的锁开销和死锁风险。对于初学阶段或中小项目物理外键其实可以用它能让数据完整性由数据库兜底减少业务代码的出错概率。但如果项目可能走向高并发尽早去掉外键约束是更稳妥的方向。6. 复制表、临时表、内存表三类特殊表的正确用法6.1 复制表结构的几种方式开发中经常要建一张和现网表结构一样的表用于测试或格式化数据。常用方式-- 只复制表结构不复制数据 CREATE TABLE user_info_copy LIKE user_info; -- 复制表结构加数据 CREATE TABLE user_info_copy AS SELECT * FROM user_info;这两种方式有一个重要区别LIKE方式会完整复制原表的字段属性、索引、默认值、自增属性是真正意义上的“结构复制”CREATE TABLE AS SELECT缩写 CTAS只会复制字段名和数据类型索引、默认值、自增属性全都不会复制。如果要用 CTAS 创建一张和原表结构一模一样的新表需要手动补索引CREATE TABLE user_info_copy AS SELECT * FROM user_info WHERE 10; ALTER TABLE user_info_copy ADD PRIMARY KEY (id);6.2 临时表的生命周期临时表用CREATE TEMPORARY TABLE创建特点是只对当前会话可见会话结束自动删除。这个特性非常适合在存储过程或复杂查询里存放中间结果。CREATE TEMPORARY TABLE tmp_order_summary ( order_date DATE, total_amount DECIMAL(10,2) );超过一定大小后临时表会被 MySQL 自动转换为磁盘表Created_tmp_disk_tables状态变量能观察影响性能。所以不要在临时表里灌入过大数据集尽量在 SQL 层面用子查询或 JOIN 替代。6.3 内存表与临时表的区别MEMORY引擎的表数据存在内存中读写速度快适合存配置项、会话信息这类小而高频的数据。但要注意两点服务重启数据会全部丢失单表大小受max_heap_table_size限制。大多数情况下现代应用更推荐把这种数据放到 Redis 里MySQL 的内存表已经逐渐边缘化了。7. 事务、锁与 DDL 的关联最容易让人误解的场景7.1 DDL 和 DML 在事务上的差异DMLINSERT/UPDATE/DELETE是支持事务的可以回滚但 DDLCREATE/ALTER/DROP在 MySQL 里执行前会隐式提交当前事务且自身不可回滚。这是很多新手在“删错表能恢复吗”这个问题上栽跟头的原因。一个典型场景START TRANSACTION; DELETE FROM user_info WHERE id 100; -- 发现删错了 ROLLBACK; -- 数据回来了 -- 但如果是 DROP TABLE user_info; -- 没有后悔药所以涉及 DDL 的变更操作务必先备份。MySQL 的物理备份用mysqldumpmysqldump -u root -p --single-transaction --set-gtid-purgedOFF \ user_db user_info user_info_backup.sql--single-transaction对 InnoDB 表可以实现一致性快照不锁表适合在线备份。恢复用mysql -u root -p user_db user_info_backup.sql7.2 行锁、表锁与元数据锁InnoDB 默认行锁但这不代表它不会锁表。当更新条件无法使用索引时MySQL 会扫描全表并对每行加锁实际效果就是整个表被锁住。所以排查“为什么我的 UPDATE 把整张表锁了”的时候先看 WHERE 条件有没有索引。8.0 之前的 MySQL 还有一个著名的metadata lockMDL问题一个长时间未提交的事务持有 MDL会导致后续的 DDLALTER TABLE一直等待。排查方法SELECT * FROM information_schema.innodb_trx; SELECT * FROM sys.schema_table_lock_waits;一旦发现 MDL 等待就去找那些开启事务但长时间未提交的会话评估后 kill 掉。这个坑在开发环境不常见一到生产环境有长事务在跑时DDL 卡住特别容易让人误以为是数据库挂了。8. 从 Navicat 到命令行库表操作的常见实战问题8.1 MySQL 8.0 的默认认证插件坑连接 MySQL 时如果报Authentication plugin caching_sha2_password cannot be loaded说明客户端版本太老不支持 MySQL 8.0 的默认认证插件。解决方案有两个升级客户端驱动到支持caching_sha2_password的版本。在 MySQL 侧把用户改回mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;注意第二种方式会降低安全性而且 MySQL 8.0 里删除mysql_native_password是长期趋势有条件还是升级驱动。8.2 命令行连接出现 ERROR 2002 的处理思路报错ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock意思是客户端试图通过本地 socket 文件连接但找不到这个文件。原因一般是 MySQL 服务没启动或者 socket 路径不对。排查顺序# 1. 检查服务状态 systemctl status mysql # 或者 service mysql status # 2. 如果服务没启动先启动 systemctl start mysql # 3. 确认 socket 路径 mysql_config --socket # 4. 连接时显式指定 mysql -u root -p --socket/var/run/mysqld/mysqld.sock如果是通过 TCP 远程连接数据库用mysql -u root -p -h 127.0.0.1 -P 3306连127.0.0.1时会走 TCP连localhost时默认走 socket 文件这个区别经常导致“我在本机能连、远程连不上”的错觉。8.3 用 SQL 检查库表整体状况接手一个陌生项目的数据库我最先执行的几条 SQL-- 查看所有库 SHOW DATABASES; -- 查看当前库所有表 SHOW TABLES; -- 查看每张表的行数和引擎等状态 SHOW TABLE STATUS; -- 查看某张表的建表语句完整还原表结构 SHOW CREATE TABLE user_info;配合information_schema数据库可以写更复杂的查询SELECT TABLE_NAME, TABLE_ROWS, ENGINE, TABLE_COLLATION FROM information_schema.TABLES WHERE TABLE_SCHEMA user_db;TABLE_ROWS对 InnoDB 表是估算值不是精确行数这个要心里有数。精确行数用SELECT COUNT(*) FROM table_name但大表执行时也要注意性能。9. 表结构设计时想清楚这几个问题能少走很多弯路9.1 范式与反范式的权衡新人学数据库原理时都背过三大范式原子性、消除部分依赖、消除传递依赖。实际开发里完全满足第三范式的表会非常碎片化查询动不动就要 JOIN 五六张表性能惨不忍睹。这时候需要做反范式设计在业务表中冗余存储一些经常展示的字段比如订单表里冗余一份商品名称就不用每次查询都去关联商品表。我的判断准则是如果这个字段几乎不会变比如商品标题、用户名昵称冗余出来利大于弊如果会频繁更新比如库存、价格冗余会导致一致性问题尽量别这么干。9.2 加字段时的兼容性考虑新增字段时允许为 NULL 还是给默认值这个决定对线上兼容影响很大。给已有表加字段MySQL 8.0 之前指定DEFAULT值会有额外限制8.0 之后支持快速加字段。但更大的隐患是如果新字段是NOT NULL且没有默认值历史数据行在读取时会出错如果给的是DEFAULT 那么新老数据行为不一致老数据是空串新写入的数据是有意义的业务值业务代码里要注意区分表里是“空串”还是“有值”。我一般倾向新加字段先NULL或DEFAULT 上线后再逐步填充数据最后再收紧为NOT NULL。一次性到位最优雅但风险也最高。9.3 删除字段比增加字段更危险ALTER TABLE ... DROP COLUMN删掉的字段数据会物理消失如果没有备份找回难度极大。我处理删除字段需求时会先确认线上日志和监控有没有依赖这个字段的查询再确认报表、导出任务、统计任务里有没有引用——曾经有次删字段删完第二天数据仓库的抽取任务直接报错就是因为漏查了一个离线任务。稳妥的做法是先停写业务代码里对该字段的写入观察一段时间确认没有报错后再执行 DROP。如果只是暂时不需要用重命名或注释标记的方式保留一段时间反而更安全。10. 分享一些我实际踩过的坑和形成的习惯写到最后说几个多年来反复遇到的经验教训。第一个是字符集问题。有一年接手的项目线上表全部是utf8而非utf8mb4用户昵称里一旦有 emoji 就变成???排查了很久才定位到是建库时没选对字符集。后面我所有新建库都是固定模板CREATE DATABASE xxx_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;这个模板已经用了很多年一次乱码问题都没再出过。第二个是 ALTER TABLE 的教训。早年在线上的用户表几千万行直接执行了ALTER TABLE ... ADD COLUMN默认用了 COPY 算法十几分钟内这张表所有写操作全部阻塞业务方电话直接打过来。从那以后我对任何超过 100 万行的表做 DDL 都会先走pt-online-schema-change再小的表也安排在低峰期。第三个是在删除操作上的固执习惯。凡是 DROP 数据库、DROP 表、TRUNCATE 表执行前必须SHOW TABLES/SELECT COUNT(*)确认一次并且确保有最近的备份。听起来很繁琐但数据库这行宁可操作多一步也不能在恢复数据上花一整天。第四个是关于临时表的滥用。早期写复杂统计 SQL 很喜欢CREATE TEMPORARY TABLE存中间结果后来发现很多场景用子查询或 CTECommon Table Expression就够了临时表创建和销毁本身也有开销。现在只有确实需要多次引用同一个中间结果集时才会用临时表而且严格控制数据量。MySQL 的库和表操作看着基础但做好做坏对后续业务开发的影响是长期的。字段类型选错、字符集混乱、DDL 锁表、索引失效这些问题的根源往往就在建库建表的那几行 SQL 里。把地基打稳后面的路才好走。
返回列表