MySQL执行流与存储引擎:一条SQL从应用到磁盘的完整旅程
你有没有过这样的经历一条 UPDATE 在测试环境秒回上了生产却偶尔卡住几百毫秒一条 SELECT 加了索引还是慢甚至在日志里看到死锁回滚。这些问题表面上是 SQL 写法、索引设计的问题但再往深处挖都会撞到两个底层概念MySQL 的“执行流”和它背后的“存储引擎”。这个系列第 04 篇我专门把这两块掰开揉碎讲透。不需要你会看 C 源码只要搞懂一条 SQL 从客户端出发到真正磁盘上的数据文件之间要经过多少道关卡以及 InnoDB 这个默认引擎到底是怎么存数据、怎么写日志、怎么处理并发后续很多“玄学”问题都会变成可推理的常识。适合刚入门 MySQL、会写基础 SQL 但不太理解数据库内部机制的同学。如果你 MySQL 还没装好先翻翻前几篇的安装和建表基础本文默认你已经能连上库、能跑查询。1. 一条SQL从客户端到磁盘中间到底卡在哪1.1 连接与线程等待你的不只是端口MySQL 是一个服务端程序默认监听 3306 端口。你在 Navicat 或者命令行里敲下密码连接层要做的不只是“验证账号密码”这么简单。MySQL 会先走一次认证流程确认用户名、密码、客户端 IP 是否允许接入接着还会查权限表确定这个会话能读哪些表、能改哪些行。这个校验结果会被缓存到连接对象里如果中途有人改了权限已经建立的连接不会立刻生效要等下次重连——这是一个很容易踩的坑线上临时授权后发现问题没生效多半就是连接没重连。连接建立后MySQL 会为这个会话分配一个线程来处理后续 SQL。线程与连接是一一对应的或者说用线程池复用反正你不需要自己做调度。但这里要理解一个关键点每个连接都占用内存和线程资源不是无上限的。如果应用层连接池配置得太激进比如一个服务开了几百个连接数据库很容易报“Too many connections”。我在实际项目里见过的正常配置单实例几百 QPS 的服务连接池上限设 50 左右就够用了核心是让连接复用而不是频繁创建。连接层在整个执行流里的位置很靠前但它常常被忽略。很多人排查慢查询只盯着 SQL 本身忘了可能是连接排队、认证耗时甚至网络延迟。一条 SQL 从应用发出到数据库真正开始解析中间隔着网络、连接池、线程调度这些耗时都会算在“总响应时间”里。所以看慢查询日志时如果发现很多 SQL 的实际执行时间很短但应用侧感觉很慢十有八九问题出在连接层或网络层。1.2 解析器从字符串到语法树连接建立之后MySQL 开始处理你发过来的 SQL 字符串。第一步是解析MySQL 内部有个解析器它会把“SELECT”“FROM”“WHERE”“id 10086”这些内容拆成一个个 token。这个过程类似你把一句话拆成主谓宾MySQL 要做词法分析和语法分析词法分析识别关键字、字段名、操作符语法分析检查语句结构是否符合规则比如 select 后面不能连续打两个逗号from 前面不能漏掉字段列表。如果这一步出错你会看到经典的“You have an error in your SQL syntax”而且错误提示会指向具体位置。解析完成后MySQL 会生成一棵“语法树”这棵树描述了这个查询的骨架我要查哪些列从哪张表查过滤条件是什么。需要注意的是此时 MySQL 并不知道这些表或字段是否真实存在。表名、字段名的合法性检查属于语义分析要查元数据才能确定。但是权限检查并不完全在这一步连接建立时已经做了一轮粗粒度的表级权限校验执行时还会有更细粒度的判断。另外提一句查询缓存。在老版本 MySQL 里解析之前会先查一遍查询缓存如果完全相同的 SQL 最近执行过并且相关表没有更新就直接返回缓存结果跳过后续所有步骤。这个机制看似很美但维护成本极高任何一个表更新都会让相关缓存失效最终得不偿失。MySQL 8.0 已经直接移除了查询缓存所以现代版本里不存在“因为缓存导致数据旧”的问题了。不要再问为什么改了数据查询结果没变那大概率是事务隔离级别或连接内缓存的问题。1.3 优化器多个执行方案里挑一个语法树通过解析后进入优化器。优化器是 MySQL 里最复杂的组件之一它的任务是给这条 SQL 制定一个“执行计划”。同样一条 SQL可能有很多种跑法可以先查表 A 再关联表 B也可以反过来可以走索引 A也可以走索引 B甚至全表扫描。MySQL 会基于表行数、索引区分度、是否排序分组等因素套用成本模型估算不同执行计划的代价选它认为最低的那个。这里能解释很多面试题和实战问题为什么我给某个字段加了索引但执行计划里索引没生效很可能是优化器觉得这个索引区分度太低走索引需要回表太多次还不如直接扫全表。为什么大表某天突然执行计划变了因为 MySQL 的统计信息不是实时精确的如果长时间没做 ANALYZE TABLE优化器拿到的行数可能和实际差很远自然就选错方案。所以定期 analyze 表尤其是数据量剧烈变化后非常有必要。优化器本身不直接干活它只产出计划。你用一个关键词就能看到它选了什么EXPLAIN。这个后面专门讲。理解优化器的意义在于当你发现一条 SQL 慢不要急着去“加索引”或“改 SQL”先想想优化器是怎么看这张表的。我曾经把一个查询改成FORCE INDEX结果反而更慢就是因为数据分布变了强制索引让优化器失去了权衡空间。大多数情况下让优化器自己选然后通过调整统计信息、重写 SQL 来影响它的判断才是正路。1.4 执行器最后一道“调度员”优化器产出执行计划后轮到执行器上场。执行器的角色其实是一个调度员它不干重活而是按计划一步步调用存储引擎的接口。比如执行计划说“全表扫描”执行器就会调用存储引擎的“读第一行”“读下一行”接口拿到一行数据后执行器会自己判断 WHERE 条件是否满足满足就放进结果集继续取下一行。很多新手容易混淆“执行器”和“存储引擎”。举个人话版例子执行器像项目经理存储引擎是施工队。项目经理不会自己搬砖他只管问施工队“下一批材料到了吗”至于材料怎么从仓库里搬出来是施工队的活。项目经理要盯进度施工队负责具体实现。这个分层也是 MySQL 架构的精髓服务层统一负责解析、优化、权限、日志存储引擎层只关心数据怎么落盘。在慢查询日志里你能看到执行器的劳动成果。字段Rows_examined表示存储引擎实际返回给执行器的行数Rows_sent表示最终返回给客户端的行数。如果两者差距巨大说明执行器在内存里过滤掉了大量行这通常意味着索引利用得不够好。调优时Rows_examined是比 SQL 耗时更稳定的参考指标因为它直接反映了扫描规模。1.5 存储引擎真正干脏活累活的角色在 MySQL 5.5 之前默认存储引擎是 MyISAM从 5.5 开始默认才变成 InnoDB。为什么会有这个变化从执行流角度理解存储引擎是跟文件系统、磁盘打交道的那一层它负责数据的物理存储、索引维护、事务控制、锁管理、崩溃恢复。同一个 CREATE TABLE你可以指定用 InnoDB 或者 MyISAMSQL 语法基本不变但底层的文件格式、数据组织方式、并发行为完全不同。还是回到执行器那个比喻执行器调用存储引擎接口时并不关心这行数据到底存在.ibd文件还是.MYD文件里它只拿到一个统一的“行”对象。这就是 MySQL“可插拔存储引擎”架构的妙处服务层的解析、优化、权限逻辑可以复用引擎层换一个插件就能改变数据存取方式。官方提供了标准接口第三方也可以写自己的引擎。对普通开发者来说这个架构带来的直接后果是你的 SQL 大部分场景不关心引擎但一旦你需要事务、行锁、高并发写入就必须深入了解 InnoDB。存储过程也一样它只是把多条 SQL 包在服务端执行最终每条 SQL 仍然要走完解析、优化、执行、引擎访问这条链路不会因为“在数据库里跑”就跳过任何一步。2. 存储引擎MySQL为什么能“换芯”2.1 可插拔架构的前因后果MySQL 很早就支持多种存储引擎这源于它开放式的架构设计。服务层像一套统一抽象层对上层隐藏了引擎差异引擎层类似于插件实现 MySQL 定义好的接口就能接入。这种设计的历史原因有很多但它带来的最大好处是灵活性业务表用 InnoDB日志表可以用 MyISAM 或 Archive临时缓存可以用 Memory。但这种灵活性也坑了不少人。最常见的翻车现场是开发在测试环境建了张表没指定 ENGINE默认引擎是 MyISAM老版本然后写事务代码、调事务接口结果数据回滚不了因为 MyISAM 根本不支持事务它也不会报错只是静默地“不支持”。所以我一直建议建表语句里必须显式写ENGINEInnoDB不要依赖默认值。尤其是在多人协作的项目里DBA 最好把 MySQL 配置文件的默认引擎统一改成 InnoDB避免有人在没注意的情况下建出非事务表。另一个容易被忽视的点是同一张表在不同 MySQL 版本里默认引擎可能不同。如果你拿 5.6 的工具连 8.0 的实例或者做数据迁移时用了 mysqldump导出的建表语句里往往会带 ENGINE 信息。但如果迁移工具漏掉了 ENGINE 子句就可能落到默认引擎上导致行为不一致。因此迁移后检查一下目标库里的表引擎分布是个好习惯。2.2 InnoDB与MyISAM的本体对比我直接用一个表说清楚 InnoDB 和 MyISAM 的核心差别维度InnoDBMyISAM事务支持 ACID提交可回滚不支持事务无法回滚锁粒度行级锁 间隙锁并发高表级锁写操作全表互斥索引结构聚簇索引二级索引叶子存主键非聚簇索引数据与索引分离外键约束支持不支持崩溃恢复依靠 redo log 自动恢复可能损坏需要 REPAIR TABLE全文索引5.6 起支持很早就支持存储结构表空间 数据页三个文件表结构、数据、索引性能特点写并发更好适合 OLTP读多写少时有低开销优势MyISAM 最大的优势是结构简单、读快、占用空间小。但它的表级锁是硬伤一条 UPDATE 会锁住整张表其他写操作全部排队读操作虽然不会被写阻塞默认配置下但客户端的查询也会因混合读写而变得不稳定。在并发高的场景你可能会看到大量线程卡在“Waiting for table level lock”这就是表锁排队。InnoDB 用行锁来减少锁竞争还加入了 MVCC多版本并发控制普通 SELECT 不需要锁就能读到一致快照。它的代价是复杂的内部机制包括事务日志、版本链、缓冲池单条 SQL 的开销可能比 MyISAM 大。但综合高并发场景InnoDB 的吞吐能力远超 MyISAM。很多人以为“InnoDB 比 MyISAM 慢”是一句真理其实在并发写入比较高的业务里MyISAM 会被表锁拖死InnoDB 却能靠行锁和缓冲机制扛住。2.3 其他引擎在什么场景下还有存在感除了两大主角MySQL 还保留了几个专用引擎只在特定场景有价值。Memory 引擎把数据全放内存读写极快但服务重启数据就没了。适合做临时表、缓存查找表、字典表不适合放业务核心数据。MySQL 8.0 里临时表默认使用 TempTable 引擎但显式指定 MEMORY 引擎还是可以的。要特别注意Memory 表使用的是固定长度行VARCHAR 也会被当成 CHAR 处理容易白占内存而且不支持 BLOB/TEXT。Archive 引擎只支持 INSERT 和 SELECT不支持 UPDATE、DELETE也没有索引8.0 已支持最多一个自增列索引。它专门做数据归档压缩比很高适合冷数据存储。CSV 引擎可以直接读写 CSV 文件方便跟外部系统交换数据但执行效率不高别用来做核心表。Federated 引擎可以跨服务器访问远程表但性能和权限都有坑现在用得很少。选择存储引擎的原则说白了一句话核心业务表一律 InnoDB临时缓存用 Memory真正需要归档、又不想上大数据冷存储的才考虑 Archive。不要为了“炫技”选冷门引擎后续维护成本完全不成比例。2.4 建表与引擎绑定实操现场实操很简单创建表时显式指定引擎CREATE TABLE user ( id bigint unsigned NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, email varchar(100) DEFAULT NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;查看当前默认引擎SHOW VARIABLES LIKE default_storage_engine;查看一张表当前用的引擎SHOW TABLE STATUS WHERE Nameuser \G也可以一次性查整个库的引擎分布SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE, TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMAtest;需要改引擎时最简单的是ALTER TABLE user ENGINEInnoDB;。但要注意这不是原地改个名字而是要重建表、拷贝数据。大表执行时会短暂占用大量 IO并且可能对线上查询造成影响。所以生产环境要谨慎至少低峰期操作更稳妥的是用 gh-ost、pt-osc 这类在线 DDL 工具或者在新实例上先做好转换再切换流量。这个坑我是在生产库上吃过亏的长记性了。3. InnoDB的底层功夫日志、缓冲池与MVCC3.1 Buffer Pool内存和磁盘之间的安全垫InnoDB 会在内存里维护一个 Buffer Pool数据页、索引页、undo 页等都会先放进这里再异步刷到磁盘。你可以把它理解成一个“快递中转站”所有数据读写都先经过它不会直接怼硬盘。读请求来时如果目标页已经在 Buffer Pool 里就是“命中”很快返回如果没命中就得从磁盘把页读进来再放在 Buffer Pool 的 LRU 链表里。InnoDB 的 LRU 不是简单的“最近最少使用”它把链表分为 young 区和 old 区防止一次全表扫描把真正热的数据全部挤出去。新读入的页默认放到 old 区只有再次被访问才会晋升到 young 区。这个设计非常实用。我见过一个案例某张 5000 万行的表业务方一次性SELECT *查全表结果 Buffer Pool 里的热数据几乎全部被淘汰后续核心查询全部走磁盘数据库负载飙升。如果你看过这个现象就会明白 InnoDB 为什么要把 LRU 分两段。Buffer Pool 大小由参数innodb_buffer_pool_size控制线上常见做法是调到物理内存的 60%~80%。如果配得太小命中率低再好的 SQL 也只能在磁盘边缘疯狂徘徊。怎么观察执行SHOW ENGINE INNODB STATUS\G看Buffer pool hit rate。如果命中率长期低于 99%就要考虑加内存或者优化访问模式了。注意配置更新后可能需要重启实例或者用动态参数修改在 5.7 可以动态调整但调大时要预留足够内存。3.2 三种日志redo log、undo log、binlogInnoDB 事务能持久化靠的是 redo log。它采用 WALWrite-Ahead Logging机制事务提交前先把这个变更写入 redo log 文件然后再修改 Buffer Pool 里的数据页。为什么这么设计因为 redo log 是顺序写磁盘性能远高于随机写而数据页的刷新可以攒一批后异步做。如果系统突然崩溃InnoDB 可以用 redo log 重放未完成的事务恢复到崩溃前状态。binlog 不属于 InnoDB它是 MySQL 服务层维护的日志记录的是 SQL 逻辑或行变更。binlog 的主要用途是主从复制和时间点恢复。它和 redo log 有几个明显区别redo log 是物理逻辑日志循环写binlog 是逻辑日志追加写redo log 记录“数据页改成了什么”binlog 记录“这条 SQL 做了什么操作”。还有一份 undo log存的是数据修改前的版本。它就像拍照时的底片用于事务回滚和 MVCC 快照读。一个 UPDATE 在执行时会先写 undo log再写 redo log然后修改数据页。事务中途失败要回滚就靠 undo log 把老版本覆盖回去。日常调优里最常被问到的“双一”参数是innodb_flush_log_at_trx_commit和sync_binlog。当两个值都等于 1 时每个事务提交都会实时把 redo log 和 binlog 刷到磁盘最安全但 IO 开销大innodb_flush_log_at_trx_commit0或2时性能更高但可能丢最近 1~2 秒的事务。核心支付场景必须“双 1”日志系统、离线统计可以适当放宽。这个取舍要看你对数据丢失的容忍度不要盲目追求性能。3.3 崩溃恢复从日志到数据页一旦 MySQL 在修改数据页时宕机Buffer Pool 里有些页是脏的磁盘上的老页可能还没更新。启动后InnoDB 会先扫描 redo log把其中记录的重做操作重新应用到数据页上如果发现某个页已经更新过就跳过。这个过程叫崩溃恢复。崩溃恢复的目标不是让你不丢任何事务而是保证已提交事务不丢、未提交事务回滚。此外InnoDB 还有 doublewrite 机制。为什么需要它因为磁盘写入不是原子操作一个 16KB 的数据页可能只写了一半就断电留下一个“部分写”的损坏页。doublewrite 会先把页完整地写到系统的 doublewrite buffer 区域再写实际数据文件。如果写入数据文件时发生异常InnoDB 可以从这个缓冲区恢复完整的页。这个机制虽然增加了写放大但对数据完整性至关重要。在极端情况下DBA 可以设置innodb_force_recovery来启动实例比如取回数据。但注意这个参数大于 0 时InnoDB 会被设置为只读或限制部分功能只能作为抢救手段。不要一听到“MySQL 起不来了”就往上调正确姿势是先备份数据文件再尝试不同的值启动能救多少是多少。3.4 MVCC让普通查询不挨锁InnoDB 的高并发很大程度上靠 MVCC。MVCC 和 undo log 版本链配合每一行数据不只存在当前值还保留着历史版本。每开始一个事务MySQL 会生成一个 ReadView里面记录了当时正在活跃的事务 ID 集合。普通 SELECT 根据这个 ReadView 决定该看到哪个版本的数据如果某个版本的事务还没提交就继续往上找更早的版本。这就带来两个不同隔离级别下的关键差异在可重复读RR下事务第一次 SELECT 时生成 ReadView并一直保持到事务结束所以整个事务里看到的数据快照是一致的在读已提交RC下每条 SELECT 都会生成新的 ReadView所以你能看到其他已提交事务带来的新数据。理解这一点很多“同样一句 SQL 在不同事务里结果不一样”的疑惑就能解开。需要注意的是MVCC 只影响普通 SELECT快照读。SELECT ... FOR UPDATE、UPDATE、DELETE这些当前读走的是最新数据并且要加锁。也就是说MVCC 不是“无锁”而是让快照读不用等写锁减少读写互斥。如果业务里出现“明明第一次查不到某行但 UPDATE 却成功了”之类的怪现象多半就是 RR 隔离级别下快照读和当前读混用导致的。搞清楚这条链路线上排查就清晰多了。4. 实战排查从执行计划到存储引擎异常4.1 EXPLAIN 到底在看什么说了一堆原理但实际排查时你第一件事就是看执行计划。MySQL 提供了 EXPLAIN 命令用法很简单EXPLAIN SELECT * FROM user u LEFT JOIN order o ON u.id o.user_id WHERE u.email testexample.com;输出结果里最值得盯的是这么几列type访问类型。从好到差大致是system const eq_ref ref range index ALL。看到ALL就是全表扫描要警惕。possible_keys优化器考虑过的索引key实际选用的索引。如果key是 NULL 但possible_keys不为空说明优化器认为用索引更亏。rows预估扫描行数。注意这是估算值不是真实值但能反映执行计划的成本。Extra重点关注Using filesort、Using temporary这两个都意味着排序或去重没能走索引通常会伴随额外临时表和排序开销。很多人说“我加了索引但没走”这时候别乱调先看看rows和possible_keys。如果数据分布太均衡比如一个性别字段区分度极低优化器走索引后还要大量回表算总账反而不如全表扫。这时候与其强扭索引不如改查询条件或者调整索引结构。另一个常见操作是ANALYZE TABLE user;刷新统计信息后执行计划可能会恢复正常。4.2 用命令行查看存储引擎信息结合本文主题你至少要知道几条命令排查时都用得上。SHOW ENGINES;能看到当前 MySQL 支持的所有引擎每一列会标出是否支持事务、XA、Savepoints。如果某个功能不支持它是 Y 还是 N 一目了然。SHOW ENGINE INNODB STATUS\G是 InnoDB 的诊断核心信息非常丰富。里面能看到当前事务列表、锁等待、Buffer Pool 命中率、日志写入情况。命令输出比较长但LATEST DETECTED DEADLOCK和TRANSACTIONS两段是排查死锁和锁等待的首选入口。SELECT * FROM information_schema.INNODB_TRX\G可以查活跃事务字段trx_started能帮你找到“跑了好几个小时都没提交”的长事务。长事务是很多问题的元凶它会阻塞其他事务、导致 undo 无法回收甚至造成主从延迟。我曾经线上出现“更新一直超时”最后发现是一条查询连着事务在应用层没提交把一行锁了 40 分钟。查看表引擎分布用前面提到的information_schema.TABLES查询即可。发现问题后修改引擎用 ALTER TABLE。但线上改引擎之前一定先评估表大小、IO 负载和业务低峰期别动不动就对几百 GB 的表直接 ALTER。4.3 常见问题速查连接失败、锁等待、排序慢把网上高频的几个问题结合起来按症状排查效率最高。第一个是error 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock。这个报错几乎每天都能看到新手提问。它大概率是 MySQL 服务没启动或者 socket 文件路径不一致。先看进程ps -ef | grep mysqld再看服务状态systemctl status mysql然后确认 my.cnf 里的 socket 路径和客户端连的路径是否一致。很多人安装时改了 socket 路径客户端还在按默认/tmp/mysql.sock连自然连不上。如果服务起了但还报错可能是权限问题比如 mysql 用户对 socket 目录没有写权限。这个错不是因为密码不对也不是因为端口不通别一上来就重装。第二个是查询卡在“Waiting for table level lock”。如果你用的是 MyISAM 表写操作会把整张表锁住读多写少时也有锁竞争。排查方法很简单SHOW TABLE STATUS WHERE Namexxx\G看 Engine 列。如果是 MyISAM就考虑ALTER TABLE ... ENGINEInnoDB。当然改引擎后要观察 SQL 是否走索引毕竟 InnoDB 的二级索引结构和 MyISAM 不一样。第三个是死锁。ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transactionInnoDB 死锁一般是因为两个事务以不同顺序加锁。比如事务 A 先更新行 1 再更新行 2事务 B 先更新行 2 再更新行 1就会互相等。解决方案是从业务侧统一同一批行的加锁顺序如果很难统一就在代码里对死锁报错做重试。查看死锁信息用SHOW ENGINE INNODB STATUS\G重点看LATEST DETECTED DEADLOCK部分它会把持锁和等待的 SQL 都列出来。第四个是排序慢Extra显示Using filesort。这说明 ORDER BY 没有走索引MySQL 需要把数据先放进排序缓冲区再排序。数据量小的时候在内存 sort buffer 里排数据量大了就会创建临时文件性能断崖式下降。思路是优化索引让排序字段包含在联合索引里或者缩短排序集的大小。比如查最近 100 条先按时间字段过滤再排而不是全表排完再 limit。4.4 优化建议先分清楚是哪一层的问题我自己踩过很多次这种坑拿到一条慢 SQL第一反应改 SQL、加索引结果毛用没有。其实慢的原因可能在执行流上层比如连接数打满、优化器统计信息不准也可能在引擎层比如 Buffer Pool 命中率太低、redo log 刷盘太频繁。所以现在我的排查顺序固定成三步。第一步用 EXPLAIN 看执行计划确认 SQL 层的扫描方式、索引选择是不是合理的。这步能排除大部分“SQL 写得烂”的问题。第二步用SHOW ENGINE INNODB STATUS看事务、锁、缓冲池信息。如果发现大量事务处于LOCK WAIT说明不是 SQL 本身慢而是大家都在抢锁。第三步回看慢查询日志对比Rows_examined和实际返回行数。如果扫描行数远远大于应用需要的行数问题大概率在索引设计如果扫描行数正常但耗时很长那就要往引擎 IO、刷盘策略上找原因。另外主从复制延迟也和底层执行流强相关。主库写 binlog从库的 IO 线程负责拉取日志SQL 线程负责回放。如果主库执行了一个几 GB 的大事务从库一次大事务回放肯定会卡住很长时间。理解了 binlog 和 SQL 线程的分工你就明白为什么大事务要拆着执行而不是一股脑提交。写在最后的一点体会这篇文章写下来我发现底层执行流和存储引擎不是两个孤立的知识点而是一条完整链路的两端。你在客户端写的每一行 SQL先被连接层接收再被解析器翻译被优化器权衡被执行器调度最后落到存储引擎的文件和日志里。无论你是刚开始学 MySQL还是已经在产线上排查慢查询把这条链路刻在脑子里遇到问题至少不会像无头苍蝇一样乱试。我个人有个习惯遇到任何数据库问题先在心里画一遍“SQL 从应用到磁盘的路径”然后决定该查哪一层。很多时候还没动手问题就定位个七七八八了。这系列到了第 04 篇MySQL 的骨架算是立住了下一步就可以进索引优化、锁与事务的进阶话题了。希望这篇能帮你少踩几个我当年踩过的坑。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →