ARTICLE DETAIL

资讯详情

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

MySQL表操作进阶指南:从建表设计到大表优化实战

MySQL表操作进阶指南:从建表设计到大表优化实战 MySQL 的表操作是几乎所有后端开发每天都要面对的事情。刚入行的时候我也觉得建表不就是CREATE TABLE加几个字段吗后来在真实项目里踩过足够多的坑——字段类型选错导致全表扫描、字符集不一致导致索引失效、大表ALTER TABLE把线上服务卡死——才意识到表的基本操作里藏着的门道比大多数教程写的要深得多。这篇文章不打算复述官方文档而是从一个实际干活的从业者角度把建表、改表、查数据、处理大表这几块里最核心的东西拆开讲清楚。不管你是刚接触 MySQL 的新手还是写了好几年 SQL 但没系统梳理过表操作细节的老手这篇内容应该都能让你有些收获。我会把重点放在“为什么这么做”上——为什么选 InnoDB、为什么varchar不能乱给长度、为什么几千万行的表删除数据不能直接DELETE理解了这些底层逻辑遇到具体问题的时候你自然知道怎么变通。1. 建表之前先想清楚设计决策比建表动作更重要很多人建表时手速飞快字段名一敲类型一选CREATE TABLE一执行就算完事。但表结构一旦上线后面再改的代价是呈指数级上升的。我参与过的项目里最惨的一次是上线半年后要把某个核心表的varchar字段从 utf8 改为 utf8mb4结果因为表里有几千万行数据在线 DDL 跑了将近一个小时期间主从延迟飙到十几分钟差点把整个业务拖垮。所以建表前多花半小时想清楚后面能省下几个通宵。1.1 存储引擎怎么选InnoDB 不是唯一答案但大多数时候是MySQL 的存储引擎是一个常被忽略却又极其关键的选择。CREATE TABLE语句里可以用ENGINEInnoDB显式指定也可以不写让它用数据库默认的引擎。MySQL 8.0 的默认引擎就是 InnoDB这基本是共识了但很多人不知道为什么要选它也不清楚其他引擎的区别。InnoDB 的核心优势在于支持事务、行级锁和外键约束。事务意味着你可以把多条操作包在一个BEGIN...COMMIT里要么全部成功要么全部回滚——这在涉及资金、订单、库存这类对一致性要求极高的场景里是底线能力。行级锁意味着并发更新不同行的时候互不阻塞相比 MyISAM 的表级锁高并发场景下吞吐量差距非常明显。MyISAM 在 MySQL 5.7 及以前还在被一些人用于读多写少的场景理由是它的查询速度在某些条件下更快、支持全文索引。但到了 MySQL 8.0MyISAM 已经基本被官方边缘化全文索引 InnoDB 也已经支持再加上它崩溃恢复能力弱、不支持事务我实在找不到新项目里选它的理由。还有一种 MEMORY 引擎数据全放内存、重启即丢一般只用来做临时表或缓存表普通业务表千万别用。实操心得如果你的表需要FULLTEXT全文索引、需要事务、需要外键或者只是不确定选什么——直接 InnoDB。MySQL 5.7 之后 InnoDB 还支持全文索引MyISAM 的最后一点优势也没了。1.2 字符集和排序规则建表时最容易被忽略的坑字符集这个问题平时安安静静的一旦出问题就是连锁反应。最典型的场景是表 A 是 utf8mb4表 B 是 utf8两张表关联查询的时候MySQL 需要对其中一列的字符集做隐式转换这一转换直接导致索引失效全表扫描。几百万行的表一次关联查询从几十毫秒变成几秒钟。MySQL 8.0 默认字符集已经是 utf8mb4排序规则是utf8mb4_0900_ai_ci。老项目里常见的是 utf8但注意 MySQL 的 utf8 其实最多只能存 3 个字节像 emoji 这种 4 字节字符是存不进去的会报Incorrect string value错误。所以现在的统一标准应该是 utf8mb4。排序规则collation里那个_ai_ci是什么意思ai是 accent insensitive即不区分重音ci是 case insensitive不区分大小写。也就是说WHERE name abc能匹配到ABC。如果你需要区分大小写就要选_bin或_cs的排序规则。这个细节在用户名、订单号这类需要精确匹配的场景里特别容易踩坑。建表时我习惯这样写CREATE TABLE user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(64) NOT NULL COMMENT 用户名, email VARCHAR(128) DEFAULT NULL COMMENT 邮箱, 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT用户表;表名和字段名都用反引号包起来不是必须的但能避免和保留字冲突——比如你有个字段叫order或desc不包反引号直接报语法错误。每个字段都写COMMENT这是给自己和同事留的文档。1.3 字段类型选型别让 varchar(255) 变成万能钥匙字段类型选型是个典型的“基础决定上层建筑”的问题。我见过太多人所有字符串一律varchar(255)所有数字一律int(11)所有时间一律datetime。短期内没问题数据量大了、查询复杂了问题就一串串冒出来。varchar的长度不是越大越好。虽然varchar(255)和varchar(5000)在存短字符串时占用的空间差不多它按实际长度存储但 MySQL 在建立临时表、做排序、做内存索引时会按照字段声明的最大长度来分配内存。一张表里来十几个varchar(255)一次ORDER BY涉及的临时表可能直接撑爆内存缓冲被迫落到磁盘上性能断崖式下跌。对于整数类型INT就是 4 字节BIGINT是 8 字节。不要为了省空间把一个将来可能超过 21 亿的 ID 字段设成INT——主键一旦溢出表就写不进去了。反过来TINYINT只有 1 字节适合存状态值0/1/2但如果你不确定状态值会不会扩展宁可SMALLINT。关于小数强烈建议不要用FLOAT和DOUBLE存金额。浮点数是近似值0.1 0.2的结果在二进制里并不精确等于0.3。金额用DECIMAL(10,2)它是定点数精度可控。时间类型里DATETIME和TIMESTAMP的差别TIMESTAMP存储范围到 2038 年且受时区影响DATETIME范围大得多不受时区影响。强烈推荐用DATETIME并且不要用INT存时间戳——你确实可以存UNIX_TIMESTAMP()但你在写查询、看数据、做报表时谁会愿意去把一列数字脑补成日期避坑建议char(32)和varchar(32)的差别char定长、varchar变长。像手机号、身份证号这种固定长度的字段用char更合适省去 varchar 的长度字节开销查询时少一次解析。虽然差别不大但细节积累起来就是性能差距。2. 建表与表结构管理从 CREATE TABLE 到 ALTER TABLE建表语句写得好不好直接决定这张表未来三五年里好不好用。这一节我把建表语法的每个关键部分拆开讲再重点说ALTER TABLE这个平时用得最多、但也最容易出事的操作。2.1 标准建表语句拆解每个关键字都在做什么一个完整的建表语句通常包含以下几个部分CREATE TABLE [IF NOT EXISTS] 表名 ( 字段名 数据类型 [NOT NULL] [DEFAULT 默认值] [AUTO_INCREMENT] [COMMENT 注释] [UNIQUE KEY ...], ... PRIMARY KEY (字段名), KEY 索引名 (字段名), CONSTRAINT 外键名 FOREIGN KEY (字段名) REFERENCES 另一张表(字段名) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT表注释;IF NOT EXISTS是一个很实用的保护措施在脚本或迁移工具里重复执行不会报错。但注意它只判断表名是否已存在如果表存在但结构完全不同它照样不执行——所以它不能替代真正的版本化表结构迁移。每个字段的NOT NULL和DEFAULT要特别重视。我的经验是业务字段能设NOT NULL就设同时给一个合理的默认值。为什么因为NULL值在 MySQL 里会带来一系列麻烦索引效率更低NULL需要额外的标记位、COUNT(column)不统计NULL值、WHERE column ! x查不出NULL值、连表关联时NULL不会互相匹配。与其让业务代码到处处理NULL不如从源头卡死。AUTO_INCREMENT一般用在主键上注意它是按表级别维护一个计数器InnoDB 在 MySQL 8.0 里已经把计数器的持久化做了优化重启不会像 5.7 那样可能回退。但对于超大规模的分库分表场景AUTO_INCREMENT无法保证全局唯一那时候要考虑雪花 ID 或分布式 ID 生成器——这就是另一个话题了。2.2 约束与默认值数据完整性的第一道防线约束的五种类型在表设计里各有各的位置PRIMARY KEY主键约束、UNIQUE KEY唯一约束、FOREIGN KEY外键约束、NOT NULL非空约束、CHECK检查约束。主键不用多说。唯一约束的应用场景很典型用户名、手机号、订单号。如果你不在数据库层面加唯一约束光靠业务代码判断“是否存在”在并发插入的时候一定会有漏网之鱼——两个请求同时查到不存在然后同时插入结果就重复了。数据库的唯一索引是并发安全的终极兜底。外键约束是个有争议的话题。教科书里告诉你外键能保证引用完整性生产环境里很多团队却明确禁用外键——原因是外键会在插入、删除、更新时触发额外的检查影响写入性能而且在分库分表后外键根本没法跨库使用。我的建议是小而美的系统可以用外键业务一旦复杂起来把外键关系放到应用层去保证数据库里只保留普通索引。MySQL 8.0.16 之后真正支持了CHECK约束之前的版本会在语法上忽略它。这个约束用在明确的值域限制上比如状态字段只能取0、1、2status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0待处理,1处理中,2已完成, CONSTRAINT chk_status CHECK (status IN (0, 1, 2))2.3 修改表结构ALTER TABLE 的正确姿势与风险ALTER TABLE几乎是每个上线日里都会出现的 DDL 语句但它也是最容易引发线上事故的操作之一。在 MySQL 5.6 之前的版本ALTER TABLE会锁全表期间任何读写操作全部阻塞。MySQL 5.6 以后推出了 Online DDL很多操作可以在线执行但并不是所有ALTER都是 Online 的。ADD INDEX在大多数场景下是 Online 的不会阻塞读写但会消耗大量 IO 和 CPU尤其是在大表上需要遍历全表数据来构建索引。MODIFY COLUMN稍微复杂一点如果只是改字段注释或默认值那是 Online 的但如果你改了字段类型或长度比如VARCHAR(64)改成VARCHAR(128)在 InnoDB 里可能需要重建表。大表执行 Online DDL 时即使不阻塞读写也会带来复制延迟问题——主库执行 DDL 期间产生的 binlog 传到从库从库执行同样的 DDL 时也会长时间占用资源导致主从脱节。解决思路有几个用pt-oscPercona Toolkit 的 Online Schema Change工具它的原理是先创建一张新结构的影子表再把老数据分批拷贝过去最后通过触发器或 binlog 同步增量数据切换表名。这个工具对超大表的在线变更几乎是标配。低峰期执行比如凌晨两三点把影响降到最低。避免频繁的、小步长的ALTER。比如一次ALTER里把所有要加的字段和索引都写了不要今天加一个字段、明天改一个字段。2.4 字段注释与表注释半年后你会感谢自己COMMENT这个东西不需要任何额外成本但大部分人就是不爱写。在代码里你写注释别人还能看得到在数据库里你不写注释这张表过半年基本就没人敢动了。我见过一个真实的案例一个运营后台的数据表status字段里存着0、1、2、9四个值代码里到处if status 1但没人知道这几只数字分别代表什么。直到某天一个刚接手的新人问“9 是什么”老员工翻了半天代码才知道是“逻辑删除”。如果当初建表时写一句COMMENT 状态0正常,1禁用,2待审,9删除这半小时的沟通成本就省下来了。表级注释同样重要一句话说明这张表是干什么的) COMMENT用户基础信息表与 user_ext 表 1:1 关联;另外ALTER TABLE ... COMMENT新注释可以随时修改表注释不影响数据。这一点可以放心用。3. 数据的增删改查DML 操作实战要点表建好了接下来就是往里填数据和查数据。这部分看起来都是标准语法但不同写法之间的性能差距可以到几个数量级。我见过同一张 500 万行的表有人写出来的查询跑 5 秒有人用同样的条件跑 50 毫秒——差的不是数据库是写法。3.1 INSERT 的几种写法与性能取舍插入数据的基础语法不复杂但有几个要点值得注意。单条插入INSERT INTO user (username, email) VALUES (zhangsan, zhangsanexample.com);批量插入INSERT INTO user (username, email) VALUES (zhangsan, zhangsanexample.com), (lisi, lisiexample.com), (wangwu, wangwuexample.com);批量插入比逐条插入快得多原因很简单每条INSERT都要经历一次事务提交如果不开显式事务的话、一次 SQL 解析、一次日志写入。把多条合并成一条这些开销都摊薄了。实测中一次插入 500 行和 500 次单行插入性能差距通常在 5 到 10 倍。还有一种INSERT ... ON DUPLICATE KEY UPDATE的用法专治“有则更新无则插入”的场景。比如用户签到、设备上报这类业务主键或唯一键存在就累加计数不存在就新建记录INSERT INTO user_stat (user_id, login_count) VALUES (10001, 1) ON DUPLICATE KEY UPDATE login_count login_count 1;这个语句在并发场景下比“先SELECT再INSERT或UPDATE”要安全得多直接避免了竞态条件。如果是大批量导入数据比如 ETL 或初始化数据还有一个LOAD DATA INFILE命令比INSERT还要快很多倍。它直接走客户端文件传输通道不需要逐条解析 SQL。不过实际使用时要注意文件格式、字符集对齐还有secure_file_priv权限限制的问题。提示插入数据时如果不需要立刻COMMIT可以手动控制事务把多条插入包在一个事务里减少 fsync 次数能明显提高吞吐量。但注意事务不要开太大否则锁持有时间太长并发一高就会互相阻塞。3.2 UPDATE 与 DELETE别忘了 WHERE 的教训UPDATE和DELETE的基本原则是永远带着WHERE并且WHERE条件尽量走索引。这句话说出来像废话但每次线上出事故十有八九是忘记带WHERE或者WHERE条件没走索引导致全表扫描。给出三个具体场景第一个误更新。我见过因为手滑把UPDATE user SET emailxxx WHERE usernametom写成了UPDATE user SET emailxxx一执行全表的邮箱全被改了。在 MySQL 里MySQL 官方其实提供了一个安全机制——在 MySQL 客户端启动时加--safe-updates参数没有WHERE条件的UPDATE/DELETE会直接报错。如果你用的客户端或管理工具支持这个选项强烈建议打开。第二个大表清扫数据。几千万行的大表想把半年之前的数据删掉如果直接DELETE FROM big_table WHERE created_at 2024-01-01会一次性锁大量行、产生巨大的 undo log 和 binlog主库卡死从库延迟整个集群被拖垮。正确做法是分批删除比如每次只删 1000 行循环执行DELETE FROM big_table WHERE created_at 2024-01-01 LIMIT 1000;每执行一次停一下观察主库负载和主从延迟正常了再跑下一批。如果删除量极大比如清了全表大部分数据更优的方案是“新建表保留数据 切换表名 删除旧表”具体流程我会在第 4 节展开。第三个更新主键或唯一键。这种做法我直接建议不要做除非你有完整的补偿方案。MySQL 里更新主键会导致行移动、索引重建、外键检查代价极高而且容易引起主从不一致。如果只是业务上需要调整主键的值大部分情况说明表设计有问题。3.3 SELECT 查询的进阶索引与回表的那些事查询优化的核心是索引而索引理解的关键在于“回表”这个概念。InnoDB 的主键索引聚簇索引的叶子节点直接存放整行数据二级索引非主键索引的叶子节点存放的是索引列的值加上主键值。当WHERE条件命中了二级索引MySQL 先在二级索引里找到主键值再拿着主键值去聚簇索引里找整行数据——这个“再查一次”的过程就叫回表。回表不是坏事它是 InnoDB 的正常工作方式。但如果查询需要回非常多的行比如WHERE status 1命中了 50 万行那就要回 50 万次表性能自然上不去。避免回表有两个思路第一个思路是覆盖索引。如果查询的列全部包含在某个索引里MySQL 直接读索引就得到结果不需要回表。比如表里有索引(user_id, login_time)你查SELECT login_time FROM user_login WHERE user_id 123那么直接从索引里就能拿到login_time这就是覆盖索引扫描。第二个思路是减少命中的行数。WHERE条件要尽量做窄能加联合索引就加联合索引能在索引里完成排序就不要在临时表里排序。还有一个高频问题是OR条件和IN条件对索引的影响。WHERE id IN (1,2,3)是能走索引的但WHERE a 1 OR b 2如果a和b上没有合适的索引MySQL 可能选择全表扫描。改写为UNION或给两个字段分别建索引效果会不一样。排序、分组和分页这几个场景也需要专门注意。ORDER BY created_at字段如果有索引排序就能在索引里完成速度快很多没有索引就要生成临时文件做 filesort。分页查询LIMIT 100000, 20的问题在于MySQL 得先查出前 10 万行再丢掉前 10 万行的回表和排序开销全白费。优化方案是“延迟关联”——先查出主键再用主键回表拿完整数据SELECT t.* FROM big_table t INNER JOIN (SELECT id FROM big_table ORDER BY created_at LIMIT 100000, 20) tmp ON t.id tmp.id;子查询里只扫主键和排序列速度能快一个数量级。4. 大表与跨表操作从几千万行说起“几千万行的大表”是网上关于 MySQL 最常被搜的话题之一。几千万行本身并不可怕MySQL 完全可以装下可怕的是在这张表上做低效查询和危险操作。这一节把大表场景下的核心问题串一遍。4.1 大表为什么慢数据页、索引与扫描要理解大表的性能瓶颈得先知道数据是怎么存的。InnoDB 的数据存储在 B 树里叶子节点对应一个个 16KB 的数据页。读数据时是以页为单位读入内存缓冲池的。几千万行的表数据页几十万个索引页几万个全部放进内存是不现实的——内存缓冲池默认只有 128MB 左右也就是最多缓存几千个页。所以查询慢的本质很多时候是“页没在内存里需要从磁盘读”。而磁盘随机读的性能比内存慢几个数量级。这就是为什么覆盖索引能优化、一级数据量大了以后要归档——目的都是减少需要从磁盘读取的数据页数量。基于这个原理大表优化的路径就很清晰了加索引把扫表变成走索引。让 SELECT 只查必要的列别SELECT *——这样每页能装更多行IO 次数少。过滤条件尽量把范围缩小MySQL 才能只读少量数据页。考虑归档和分区把老数据拆走。另外COUNT(*)在大表上依然是个痛点——InnoDB 不维护总行数每次都要实时计算。如果你需要频繁获取大表的行数一个常规做法是单独维护一张计数器表或者用近似值兜底SHOW TABLE STATUS里的行数是估算值。4.2 跨表合并与多表关联的实践跨表合并这个词在不同语境下意思不太一样。有人说的是JOIN多表关联有人说的是把多张结构相似的表UNION ALL合并成一份数据。这两类在“表的基本操作”里都绕不开我把要点分别说一下。先讲JOIN。多表关联时表的连接顺序和索引策略直接决定查询效率。MySQL 优化器会自己选执行计划但理解它的几个基本原则对排查问题很有帮助JOIN通常从驱动表开始对驱动表的每一行在另一张表的连接列上做索引查找。所以一个常见的优化手段是让小表做驱动表大表的连接列必须有索引否则大表侧会出现全表扫描。加两条实战经验第一连接列的数据类型必须一致字符集和排序规则也要一致否则索引失效第二LEFT JOIN时如果右表有重复记录结果会变成笛卡尔积式的膨胀这种问题极难排查建议加唯一约束或提前DISTINCT。再说UNION和UNION ALL。当你要把几张结构相同的表合并查询比如按月份分表的历史数据UNION ALL比UNION快得多——因为UNION会去重需要额外的排序和比较操作。两张表结构本来就不会重复的话直接用UNION ALL。SELECT user_id, order_amount FROM orders_2024_01 UNION ALL SELECT user_id, order_amount FROM orders_2024_02 UNION ALL SELECT user_id, order_amount FROM orders_2024_03;这种“跨表合并”查询在分表场景下很常见但如果你分了很多张表每次都要写一大串UNION ALL维护起来很痛苦。下一节会提到视图可以怎么简化这个操作。4.3 在线 DDL 与锁表问题“锁表”是 MySQL 里最让人头疼的问题之一。一个ALTER TABLE或者一个长事务都可能把表锁住导致所有读写请求排队。MySQL 8.0 的 Online DDL 机制已经比老版本好很多但锁表事故还是时有发生最常见的根因其实不是 DDL 本身而是长事务。长事务的意思是一个事务从BEGIN到COMMIT之间隔了很长时间哪怕它只是执行了一条SELECT都会导致 undo log 不能清理甚至阻塞后续的 DDL 操作。排查长事务的方法SELECT * FROM information_schema.innodb_trx ORDER BY trx_started;这个查询能列出所有正在执行的事务trx_started最早的往往就是问题所在。看到长时间未提交的事务先确认它是否在跑大查询还是业务代码里忘了提交。把长事务干掉很多莫名的锁等待问题就消失了。ALTER TABLE执行时如果是 Online 的虽然不阻塞读写但在主从复制架构下从库执行同样的 DDL 时会有延迟。如果想减小影响前面提到的pt-osc是一个好选择它把 DDL 拆成“建新表、拷数据、换表名”的流程对业务的影响比直接ALTER更平滑。注意pt-osc要求表必须有主键或唯一键它靠这个键来分批拷贝数据。没有主键的表pt-osc直接拒绝执行。这又是一个“建表时必须主键”的理由。4.4 视图与表的解耦视图在表基本操作里应该被更多人重视。视图本质上是一条保存下来的 SQL 查询不占额外存储每次查询时实时执行。它适合做三件事权限控制只暴露某些列、简化复杂查询、屏蔽底层表结构变化。比如前面说的跨月分表可以用视图把UNION ALL封装起来CREATE VIEW v_all_orders AS SELECT user_id, order_amount FROM orders_2024_01 UNION ALL SELECT user_id, order_amount FROM orders_2024_02;之后业务查询直接SELECT * FROM v_all_orders不需要关心底层到底有多少张表。不过要注意视图也有性能陷阱如果视图里套了多层子查询和关联MySQL 可能生成临时表性能反而不如直接查底层表。用视图做简化没问题但如果视图查询变得很慢要记得回到底层表去分析执行计划。5. 常见问题排查与实战技巧最后这部分我把平时群里、论坛里看到的高频问题整理成速查表再分享几个我自己的排查习惯。这些内容大多是经验层面的写出来是为了让大家少走弯路。5.1 建表与表操作问题速查表下面这张表覆盖了日常工作中最常遇到的几类问题每一类都给出了原因定位和解决方向问题现象可能原因解决思路插入中文或 emoji 报Incorrect string value表字符集不是 utf8mb4改表字符集为 utf8mb4并确保连接字符集一致查询很慢EXPLAIN显示typeALL没有可用索引或索引因类型/字符集不一致失效检查EXPLAIN里的key列补索引或统一类型分页很深时越来越慢大偏移量导致大量回表和排序用延迟关联或游标分页基于上次结果的位置复制延迟高大事务、DDL 或DELETE批量过大拆分事务、低峰期 DDL、分批删除DELETE执行后表文件没变小InnoDB 删除数据是逻辑删除空间不立即释放用OPTIMIZE TABLE重建表释放空间注意会锁表ALTER TABLE卡住不动有长事务持有表级元数据锁information_schema.innodb_trx找到阻塞事务并处理两个表关联结果不一致字符集、排序规则、字段类型不一致统一两边的字符集和数据类型唯一键重复但代码没发现并发插入导致竞态条件数据库加唯一键应用层处理冲突这个表格不是放这里好看的我建议你把它存在本地备忘里遇到问题先对着排查一遍很多时候能省下大量靠猜的时间。5.2 排查性能问题时必用的命令排查 MySQL 性能问题有几个命令和 SQL 是绕不开的基本功。第一个是EXPLAIN。任何慢查询第一步都是拿它的执行计划看看EXPLAIN SELECT * FROM user_login WHERE user_id 10001 ORDER BY login_time DESC;重点看四列type访问类型const、ref、range都是好的ALL是全表扫描要警惕、key实际用到的索引、rows预估扫描行数、Extra如果出现Using filesort或Using temporary说明排序或去重用了临时表需要优化。第二个是慢查询日志。MySQL 的慢查询日志记录了执行时间超过阈值的 SQL开启方式是在配置里设置slow_query_logON和long_query_time1超过 1 秒算慢查询。定期翻慢查询日志比被动等用户报障要有效得多。第三个是SHOW PROCESSLIST。它能看到当前所有正在执行的连接和 SQL排查“数据库卡住”问题时的第一反应就应该是它——看看有没有Waiting for table metadata lock状态有没有SELECT跑了特别久有没有Sleep状态的连接堆积。MyS5.3 表操作的习惯与禁忌清单最后整理几个我认为真正重要的习惯和禁忌。第一个习惯任何手工操作UPDATE、DELETE之前先SELECT一遍同样的条件确认你要操作的行数比自己想象的要少得多。有条件的话先把要改的行查出来BEGIN开启事务再执行UPDATE确认影响行数正确后再COMMIT。错了还能回滚。第二个习惯涉及主键字段的修改、涉及全表数据的类型变更、涉及几千万行表的索引新增这三类操作不要直接在生产库上拍脑袋执行。先在测试环境还原同样的表结构和数据量评估执行时间和影响再定方案。第三个习惯永远不要让业务代码在循环里一条条执行 SQL。不管是插入、更新还是删除循环里调 SQL 意味着成千上万次网络往返、日志刷盘、SQL 解析——这是新手最容易写出来的、对数据库伤害最大的写法。改成批量语句性能提升立竿见影。还有一个禁忌不要在线上随便执行DROP TABLE。如果真的需要删表先RENAME TABLE xxx TO xxx_bak_20240101留一天或一周的“后悔期”确认没有程序在依赖它再物理删除。这个习惯救过我很多次。我自己在实际项目中还有一个体会表的基本操作从来不只是“SQL 语法”的问题而是“数据生命周期管理”的一部分。从建表时的字段设计、默认值和注释到上线后的索引维护、数据归档、问题排查每个环节都值得用工程化的标准去对待。养成这些习惯需要时间但一旦形成肌肉记忆你在任何团队里都会成为那个“数据库不用操心”的人。
返回列表