ARTICLE DETAIL

资讯详情

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

MySQL查询全链路拆解:从连接建立到结果返回的完整过程

MySQL查询全链路拆解:从连接建立到结果返回的完整过程 有一次帮同事排查一个线上问题应用日志里刷的全是“无法连接到数据库”MySQL 的 CPU 占用正常、网卡也没异常可连接就是建不起来。折腾了半天最后才发现是连接数被打满了而 TCP 层根本没报任何错所有异常都被“连接”这两个字挡在了门外。那次之后我就格外认同一个观点用 MySQL 如果只关心 SQL 本身不把从连接建立到查询返回的整条链路理解透彻遇到问题的时候基本只能靠猜。这篇文章我想顺着一次真实查询的路径把 MySQL 从连接数据库到查询的全过程完整拆一遍TCP 三次握手MySQL 自己的协议握手认证和 SSL 协商SQL 从文本到语法树的解析过程优化器的成本计算执行器与 InnoDB 的配合再到结果集返回。不是背文档而是把每条链路上“为什么是这样”讲清楚。不管是刚入门的开发还是写了几年 SQL 的老手应该都能从中找到一些以前忽略的细节。1. 一次查询的完整旅程连接、解析、优化、执行、返回五段链路谁也少不了很多人写 SQL 很熟练但问到“这条 SQL 从客户端发出去之后服务器到底做了几件事”往往只能回答“数据库执行了”。实际上一次看似简单的查询在 MySQL 内部是被拆成若干独立阶段处理的每一阶段都有自己的模块、自己的参数、自己的故障表现。搞懂整条路径最大的价值在于当故障发生时你能把问题精确地定位到某一环而不是对着整台数据库瞎折腾。1.1 五段链路分别是什么按数据流动的方向一次查询大致经历这几个阶段阶段对应模块一句话作用典型故障表现1. 连接建立网络层、连接器Connection Manager客户端与服务器完成 TCP MySQL 协议握手通过认证连接超时、Access denied、Too many connections2. SQL 传输与解析协议层、解析器ParserSQL 文本变成服务器能理解的语法树语法错误、max_allowed_packet 超限3. 查询优化优化器Optimizer为语法树选择成本最低的执行方案慢查询、选错索引4. 执行执行器 存储引擎如 InnoDB按执行计划逐行读取、过滤、计算锁等待、IO 瓶颈、内存命中率低5. 结果返回协议层、客户端驱动服务器把结果集打包送回客户端丢连接、客户端内存溢出这个拆分不是我拍脑袋分的它基本对应 MySQL 服务端源码里的各个模块。在 5.7 以前的版本里解析之前还有一道查询缓存查询8.0 里查询缓存被彻底移除了原因后面我会专门讲。1.2 用点外卖类比一次查询如果觉得链路太抽象可以想象你点了一份外卖连接建立是“打通餐厅电话”SQL 传输是你“报出菜名”解析器是前台“听懂你在说什么”优化器是后厨“决定这道菜怎么做最省时间”执行器是“厨师动手炒菜”InnoDB 是“灶台和冰箱”结果返回就是“外卖骑手把菜送到你手上”。这个类比有个好处它能解释为什么“炒菜”本身优化的空间有限但整个链路里任何一个环节拖延你都会觉得“这单好慢”。很多慢查询排查到最后问题根本不在 SQL 执行而是在连接阶段就在排队这一点没有全链路视角的人是很难意识到的。2. 连接建立不只有 TCP 三次握手还有 MySQL 自己的协议握手连接是所有查询的第一步。这一步出问题的概率在我的经验里比 SQL 本身出错还要高。原因是它牵扯到的协议层次多TCP 层、TLS 层、MySQL 协议层、认证层任何一层对不上连接就建立不起来。2.1 从 TCP 三次握手说起客户端要连上 MySQL首先是 TCP 层的连接。假设 MySQL 在 192.168.1.10 的 3306 端口监听客户端发起连接时会完成经典的三次握手SYN、SYN-ACK、ACK。这三次握手如果完不成客户端会报网络超时之类错误应用层根本见不到 MySQL 的响应。很多人容易忽略的是TCP 连接建立成功不代表 MySQL 就“准备好收 SQL”了。3306 端口上的监听由操作系统完成listen 队列满了back_log 参数控制时即使服务端进程还活着新连接也会在 TCP 层被挂起。我遇到过一次诡异故障应用端一直报连接超时但数据库 CPU、内存都正常最后发现是短连接风暴把 backlog 打满了。这种问题不看 TCP 层是定位不到的。2.2 MySQL 协议握手服务器先开口发一个 HandshakeV10TCP 连接建立后MySQL 服务器会主动发送一个握手初始化包协议里叫 HandshakeV10。这个包里包含的信息非常多关键有这么几个协议版本号通常为 10服务器版本字符串比如“8.0.36”本次连接的线程 IDthread id后续 show processlist 里能看到认证插件名和随机数salt / scramble能力标志位capabilities flag用来声明服务器支持的能力比如是否支持 SSL、是否支持二进制协议、是否支持多结果集。客户端收到握手包后要根据能力标志位决定自己用什么方式回复。如果客户端的能力和服务器对不上就会出现“协议版本不匹配”或某些高级功能不可用的问题。这里有个细节客户端连接参数里的字符集、是否使用 SSL、是否允许压缩都是在这一轮协商的。客户端随后发送握手响应包HandshakeResponse41里面带上用户名、目标库、认证数据、客户端能力标志等。服务器验证通过后会回一个 OK 包验证失败回一个 ERR 包。到这一步连接才算真正建立。2.3 认证与 SSL 协商谁先谁后密码到底怎么验证很多文档会把 SSL 和认证混在一起讲容易让人糊涂。实际顺序是客户端在握手响应包里声明“我请求使用 SSL”通过能力标志位服务器同意后先完成 TLS 握手然后客户端再把认证数据密码加盐后的哈希放到 TLS 保护的信道里发送。也就是说SSL 协商发生在上面的 TCP 握手之后、认证数据发送之前。MySQL 8.0 默认认证插件是 caching_sha2_password5.7 及更早版本默认是 mysql_native_password。这两者有个明显的区别在非 SSL 连接下caching_sha2_password 首次认证时客户端需要额外请求服务器的 RSA 公钥来完成密码传输所以如果你用老客户端直连 8.0又没配置 RSA 公钥经常会报 authentication 相关的错误而 mysql_native_password 不需要这一轮交换所以老生态里它一直“看起来更省事”。我生产环境的习惯是能开 TLS 就开 TLS尤其跨机房访问数据库的时候。虽然require_secure_transport会带来一些性能开销和配置成本但数据库密码和查询内容在网络上明文飘是我不想承担的风险。2.4 长连接、连接池和几个最常见的连接参数MySQL 建立一次连接的消耗其实不小一次 TCP 三次握手一次协议握手一次认证交互再加上可能的 TLS 多重握手。所以生产环境几乎没人用“一次查询一个连接”的方式基本都靠连接池复用长连接。连接池怎么配比大多数人想的要讲究。太小的池子会在高并发时排队太大的池子会反过来把数据库连接数打满。我的一个经验连接池上限不要超过 MySQLmax_connections的百分之八十留出给 DBA 和运维排查问题的余量否则一旦应用发疯DBA 连数据库都登不上去。连接阶段几个关键的 MySQL 参数建议直接记下来参数默认值作用max_connections151最大连接数超过后拒绝新连接connect_timeout10s服务器等待握手响应包的超时wait_timeout28800s非交互连接空闲超时interactive_timeout28800s交互式连接空闲超时back_log80TCP listen 队列长度max_connect_errors100单机连接中断错误次数阈值超过后暂时拒绝该 IP一个容易踩的坑是max_connect_errors。客户端和服务器之间网络抖动导致大量 TCP 连接断开重连错误次数累计超过阈值后MySQL 会直接拒绝这个客户端的 IP报 “Host ... is blocked”。我第一次见到这个报错时还以为是防火墙实际上清掉缓存就行但更重要的是检查底层网络为什么抖动。还有一个常见的 SSL 连接错误客户端报 SSL 相关错误、服务器日志里是 TLS 版本不匹配或证书过期。我的排查流程先确认服务器have_ssl状态再确认客户端连接参数里的ssl-mode是否和服务端配置一致最后检查证书有效期。很多时候根本不是密码错而是客户端强制要求 SSL服务器却压根没开 SSL。3. SQL 进服务器之后解析器的活和预处理阶段那些“隐藏校验”连接建立后客户端终于能把 SQL 发出去了。很多人以为这是“把字符串丢给数据库”但这条路径上其实有协议封装、网络传输、解析、预处理好几道关卡。3.1 COM_QUERY 与文本协议SQL 是怎么封装成报文的MySQL 客户端和服务器之间用的是 MySQL 自有协议不是 HTTP。客户端发送查询时会组装一个报文第一个字节是命令类型COM_QUERY对应的值是 0x03后面跟着 SQL 文本。服务器读到这个报文后才知道“客户端是在给我发查询而不是发 ping 或者其他管理命令”。SQL 文本的大小不是无限的受max_allowed_packet限制。这个参数默认是 64MB但两端都要配客户端配置和服务器配置不一致时可能出现“大 SQL 发不出去”或“大结果集收不回来”的报错。生产环境里我一般会把两端都调大但调大之前会先看业务是否真的需要那么大的报文而不是无脑改。服务器网络层收到报文后会存放在内存 buffer 里。如果一条 SQL 超过了max_allowed_packet服务端会直接关闭连接——这点很多新手不知道他们看到“Lost connection”时还在纠结是网络问题其实是包太大被拒了。3.2 词法分析、语法分析从字符串到语法树SQL 到了服务器第一步是解析Parsing。解析分为两层词法分析负责把 SQL 字符串拆解成一个个 token。比如SELECT name FROM user WHERE id 1会被拆成 SELECT、name、FROM、user、WHERE、id、、1 这些独立的词。这个环节出错通常是写了不认识的字符或者关键字拼写错误。语法分析则把 token 按 MySQL 的语法规则组合成抽象语法树AST。这一步如果有问题报的错误就是那种一看就知道“SQL 写错了”的语法错误比如少了括号、少了引号、错用了保留字。很多人为了省事用拼接字符串的方式构造 SQL然后在语法分析阶段被拦下来才意识到问题。我见过最魔幻的报错是 SQL 里嵌了对数据库来说完全不可见的 Unicode 字符肉眼看起来一模一样但词法分析就是过不去。遇到这种“代码没变但突然报错”的情况先把 SQL 的十六进制 dump 出来看一眼基本能定位。3.3 预处理表、列、权限一步都不能少语法分析通过后还有一道预处理。这一步里MySQL 会检查SQL 里引用的表是否存在引用的列是否存在列名有没有歧义展开SELECT *真正的列列表检查当前用户对这些表和列的权限。所以你写SELECT * FROM no_such_table报错的时机其实在优化器之前预处理阶段就把你拦下了。权限检查的细节也值得注意MySQL 的权限是基于用户主机匹配的比如app192.168.%只允许特定网段连接如果客户端 IP 不在授权范围里认证阶段就会被拒报 Access denied。这个看似基础的问题在容器化和 Kubernetes 环境里非常常见——Pod IP 每次重启都会变权限里写死的 IP 没更新应用就连不上数据库。3.4 为什么 8.0 把查询缓存删了MySQL 8.0 之前解析阶段还有一个“查询缓存”的检查。如果之前执行过一模一样的 SQL 且缓存没有被失效服务器会直接返回缓存结果跳过后面的优化和执行。听起来很好对吧但查询缓存的失效机制非常粗暴只要相关表发生了任何写操作该表的所有查询缓存全部失效。这意味着写多读少的业务里查询缓存不仅帮不上忙反而因为频繁加锁失效缓存成为高并发的瓶颈。我见过一个 5.7 的项目关掉 query cache 之后写性能反而涨了一截。8.0 直接把它删了从代码层面结束了这场争论。这个例子很好地说明MySQL 很多“看起来加速”的机制实际收益要结合负载特征看不能只看字面上的效果。4. 优化器的选择成本模型、索引评估以及它偶尔误判的时刻SQL 过了解析和预处理就轮到优化器出场了。优化器被很多人当成“黑魔法”其实它的核心逻辑是算账在多种执行计划里挑一个它认为成本最低的。理解它的算账逻辑比背一堆“优化技巧”管用得多。4.1 优化器是在“算账”成本模型与统计信息MySQL 的优化器是基于成本的。每一类操作都有成本值比如读一个数据页的成本、比较一行的 CPU 成本、顺序读和随机读的成本差异。这些成本值可以在mysql.server_cost和mysql.engine_cost两张表里看到并调整。优化器估算成本依赖的是统计信息。InnoDB 的统计信息不是精确的而是通过随机采样估算出来的主要包括表的行数、索引的基数cardinality即索引中不同值的数量。这也是为什么在大表数据变化剧烈之后执行计划可能会“跑偏”——统计信息过期优化器手里的数据是旧的。这时候执行ANALYZE TABLE重新收集统计信息往往就能恢复正常。4.2 索引选择实例为什么同样一条 SQL换一个索引差了几个数量级举一个很典型的例子。表orders上有两个单列索引分别建立在customer_id和status上SELECT id, amount FROM orders WHERE customer_id 12345 AND status PAID;优化器需要判断用哪个索引更划算。如果customer_id 12345能命中 100 行而status PAID能命中 10 万行优化器显然更倾向于用customer_id的索引。但如果这 100 行的数据分布在 100 个不同的数据页上而statusPAID的 10 万行是聚集存储的优化器可能会算出完全不同的结论。这个例子说明两件事第一索引的选择不是看“有没有索引”而是看“索引的区分度和数据分布”第二实践中联合索引往往比多个单列索引更有效因为联合索引 (customer_id, status) 能在一棵 B 树里同时过滤两个条件避免回表。4.3 优化器误判与干预手段optimizer_trace 到底怎么用优化器也会误判。常见原因包括统计信息过期、隐式类型转换导致索引失效、OR 条件被拆分成多个计划时估算不准、多表连接时表顺序选错等。遇到这种情况我建议先用optimizer_trace看优化器的完整决策过程SET optimizer_traceenabledon; SELECT * FROM orders WHERE customer_id 12345; SELECT * FROM information_schema.OPTIMIZER_TRACE\G SET optimizer_traceenabledoff;OPTIMIZER_TRACE会输出优化器每一步的考虑包括它比较了哪些候选索引、估算行数是多少、最终为什么选了这个方案。有了这个你就不需要“猜”优化器为什么犯傻直接看到它的算账过程。干预手段通常是这几条重新收集统计信息、FORCE INDEX指定索引、改写 SQL 让优化器更容易走你想要的路径或者调整optimizer_switch开关某些优化策略。这里顺便提一下EXPLAIN的关键列。type列的值能直接反映访问方式从好到差大致是const / eq_ref / ref / range / index / ALL。如果一条大表查询的type是ALL说明全表扫描这在绝大多数业务里是不能接受的。type 值含义是否可接受const / eq_ref主键或唯一索引等值查找非常好ref普通索引等值查找好range索引范围扫描较好index全索引扫描一般优于 ALLALL全表扫描通常不可接受5. 执行器与 InnoDB 的配合缓存、MVCC还有真正的“干活”环节优化器输出执行计划后执行器开始按照计划读取数据。这层的核心是三件事执行计划怎么被逐行执行、InnoDB 在背后做了什么、以及怎么观察执行细节。5.1 执行器按计划逐行指挥执行器可以理解成一个调度器。它从执行计划的第一步开始调用存储引擎的 API 去读记录然后做条件过滤、投影、排序、分组等操作。像WHERE条件的过滤执行器这层会处理一部分但 MySQL 8.0 引入的“索引下推ICP”优化把部分条件过滤下推到存储引擎层减少回表的次数。这也是为什么同样的 SQL8.0 和 5.7 的执行计划可能不一样。执行器的线程模型也值得一提。MySQL 经典模型是一个连接一个线程每个客户端连接对应服务器上的一个线程。并发连接数就代表线程数线程太多时会出现上下文切换开销大的问题。线程池能缓解但默认社区版没有需要特定版本或插件支持。所以在高并发场景下控制连接数比盲目调线程更实在。5.2 InnoDB 在普通 SELECT 里做的事Buffer Pool 与 MVCC执行器要求 InnoDB“返回某行数据”时InnoDB 的工作大致是从索引 B 树定位到对应记录所在的数据页然后读数据页。如果数据页已经在 Buffer Pool内存缓存里这次就是纯内存操作不在的话就要从磁盘读入 Buffer Pool。所以一条查询快不快很大程度上取决于数据页能不能命中 Buffer Pool。innodb_buffer_pool_size这个参数是 InnoDB 最重要的性能参数没有之一。我见过很多“明明数据库负载很低但查询就是慢”的案例最后都是 Buffer Pool 太小大量 IO 在磁盘上排队。经验值是把 Buffer Pool 设到机器物理内存的 60%-75%前提是这台机器是专用数据库服务器。普通SELECT的另一个关键机制是 MVCC多版本并发控制。简单理解在一个事务里执行普通SELECTInnoDB 会基于事务的隔离级别创建一个“读视图”read view查询只读到该视图可见的版本。这样读操作不需要加锁不会阻塞写操作写操作也不会阻塞读操作这就是 InnoDB 并发读写的底气。如果执行的是SELECT ... FOR UPDATE那就是“当前读”会加锁要等锁释放超时受innodb_lock_wait_timeout控制默认 50 秒。5.3 Handler 状态变量与执行细节的观察方式执行器每调用一次存储引擎接口服务器会更新对应的状态计数器这些计数器在SHOW GLOBAL STATUS里能看到名字带Handler_前缀。常用的几个Handler_read_first读索引第一行通常代表从索引头开始扫描Handler_read_next按索引顺序读取下一行代表走索引范围扫描Handler_read_rnd_next随机位置读下一行全表扫描时这个值会猛涨Handler_read_rnd普通文件位置读取排序后读取常导致这个值增长。如果你看到一条查询的Handler_read_rnd_next异常大基本可以断定发生了全表扫描。这个信息和EXPLAIN的typeALL可以互相印证。执行器的慢查询记录也有讲究。long_query_time是慢查询阈值默认 10 秒log_queries_not_using_indexes ON会把所有没有用索引的查询记录到慢日志里这个开关建议打开哪怕查询本身很快全表扫描也值得警惕因为它是潜伏的慢查询种子。6. 结果集返回从服务器到客户端最后一段路也有讲究查询执行完数据已经取出来了但工作还没结束。服务器要把结果集编码成 MySQL 协议报文通过网络送回客户端客户端再把报文解析成程序里的数据结构。这一段路的坑往往比执行阶段更隐蔽。6.1 结果集协议列定义、数据行、EOF 包服务器返回查询结果时会依次发送三类报文列定义包描述每一列的元信息列名、表名、类型、字符集等数据行包一行一行地把数据按协议编码发出去结束包EOF / OK标记结果集结束。MySQL 的结果集有两种编码方式。最常用的是文本协议所有数据都以字符形式传输比如数字 12345 会变成字符串“12345”客户端拿到后再转成整数。另一种是二进制协议主要用于 Prepared Statement预编译语句数据以二进制编码传输更省空间也更快。JDBC 里用了预编译语句时底层走的就是二进制协议。一个经常被忽略的点是每列的类型信息和字符集。如果表字段是 utf8mb4客户端连接的字符集也是 utf8mb4传输不会出问题一旦表是 utf8mb4客户端连接用的是 latin1 或连接参数没写字符集就可能出现乱码或写入失败。字符集的坑我踩过太多次了建议在连接串里显式指定别依赖服务器默认值。6.2 流式读取 vs 全量拉取客户端处理结果集的大坑很多语言的驱动在默认情况下会把服务器返回的结果集一次性读完存到客户端内存里。一两条 SQL 没问题但如果你跑一个返回几百万行的查询客户端内存会瞬间被撑爆——程序“本地内存不足”锅却常常被甩到数据库头上。解决方式是流式读取。以几个主流语言为例JDBC 里可以通过useCursorFetchtruedefaultFetchSize500开启服务端游标分批次取数据PHP mysqli 里用MYSQLI_USE_RESULT模式而不是默认的MYSQLI_STORE_RESULTPython 的 PyMySQL 可以用SSCursor实现同样的流式效果。流式读取的代价是在结果集没有完全取完之前当前连接会被“占用”不能执行其他操作否则协议状态会错乱。所以流式读取适合专门用于导出类任务不适合做普通的业务查询。6.3 结果返回阶段最隐蔽的问题超大结果与超时结果集返回阶段有两个问题最隐蔽。第一个是max_allowed_packet。如果某条查询的结果集、或者某一行数据大小超过这个限制服务器会中止连接客户端报 “Lost connection to MySQL server during query”。出现这个报错时很多人会先怀疑网络其实往往是把足够大的数据塞进了一个不够大的包里。第二个是网络写超时。服务器把结果集通过 TCP 发送给客户端时如果客户端长时间不读数据比如客户端处理太慢、或者网络延迟很高服务器会因为net_write_timeout默认 60 秒认为对方“不收了”主动断开连接。反过来如果客户端一直发数据但服务器读不到会命中的是net_read_timeout。两种超时一个管写、一个管读排查时记得分清楚方向。我还有一个经验跨地域访问数据库时结果集的网络传输时间经常比 SQL 执行时间还长。优化这类查询时别只盯着执行计划先算算这条查询要返回多少行多少字节网络带宽是不是瓶颈。该做分页就分页该按需取列就别SELECT *。7. 排查实践当连接失败或查询变慢时我按什么顺序逐个定位理解了全链路之后排查问题的思路就清晰了先把故障现象映射到链路的某一环再针对那一环深入检查。这是我从“瞎猜型排查”变成“链路型排查”的关键转变。7.1 连接失败类问题的排查顺序连接失败的报错五花八门但按链路顺序看其实很好归类。我的排查顺序从来都是固定的网络可达性ping数据库 IP再用telnet 数据库IP 3306或nc -vz看端口是否可达。这一步能排除基础网络和防火墙问题。服务状态在数据库本机用mysql -uroot -p通过本地 socket 连一次能连上说明服务正常问题在远端连不上就得查 MySQL 进程和错误日志。TCP 层队列如果应用报连接超时、但服务正常检查back_log和当时 TCP 的连接建立情况看看是不是 listen 队列满。SSL 协商客户端告警 SSL 相关错误时先核对两端ssl-mode、证书、TLS 版本再看require_secure_transport是否开启。认证授权Access denied时确认账号的 host 匹配、密码、默认认证插件是否对得上。连接数饱和登录 MySQL如果还有超管连接权限执行SHOW STATUS LIKE Threads_connected;看是否逼近max_connections。如果内存够可以临时调大但根本解法是压住应用端的连接池别让无用连接占坑。这套流程走下来连接类问题基本没有漏网的。有个细节连不上数据库时优先用本地 socket 登录检查而不是继续从远端试——本地登录绕开了网络层能快速区分“网络问题”和“MySQL 自身问题”。7.2 查询慢的定位EXPLAIN 之后还要看什么一条查询变慢我会从这几个层次逐层看第一步是EXPLAIN看执行计划类型、索引选择、预估行数。如果type到了 ALL或者key是空先解决索引问题。第二步是EXPLAIN ANALYZEMySQL 8.0 提供比如EXPLAIN ANALYZE SELECT ...这里会输出实际执行时间和每一阶段的实际行数能和EXPLAIN的预估行数对比判断优化器的估算是否失真。第三步是optimizer_trace看优化器为什么选了这个索引、估算依据是什么。这步特别适合“明明有大索引却全表扫描”的诡异情况。第四步是看执行器层状态SHOW GLOBAL STATUS LIKE Handler_read%和SHOW ENGINE INNODB STATUS判断是不是全表扫描、锁等待、或 Buffer Pool 命中率过低。最后一步是结合慢查询日志用本地的pt-query-digest之类的工具把慢 SQL 按模板聚合排序。这一步能发现“单条 SQL 不慢但同一模板数量极多拖垮了数据库”的典型场景。7.3 一个排查链路实例慢查询最终栽在字符集上分享一个我印象深刻的案例。某天一条SELECT * FROM user WHERE phone 13812345678突然从 20ms 变成 3 秒EXPLAIN 显示走的是主索引但 rows 估算异常大。反复看表结构phone是 varchar 类型数据没问题索引也在。后来用SHOW CREATE TABLE和SHOW VARIABLES LIKE collation_%对比才发现表字段的排序规则是utf8mb4_general_ci而连接字符集是utf8mb4_unicode_ci两边在索引合并和比较方式上出现了一致性问题优化器的行数估算全乱了。把连接字符集和表字符集对齐之后查询恢复到了毫秒级。这个案例给我的教训是执行计划只是表象底层的数据类型、字符集、排序规则这些“元信息”才是决定优化器判断的基础。出问题的时候不要只盯着 SQL 本身把表结构、字符集、连接参数拉出来一起看经常能找到真正的根因。7.4 给新人的三个建议如果只让我给三条可落地的建议分别是务必把本文这条链路画下来贴在自己能看到的地方。排查问题时先判断是哪一环再动工具而不是一上来就EXPLAIN。给数据库设一套“体检清单”定期执行连接数和max_connections的比例、Buffer Pool 命中率、线程数、慢查询数、Handler_read_rnd_next的波动。这套清单其实就是把链路的每个环节都监测一遍。不要迷信任何优化技巧包括这篇里写的。每一台数据库的负载都不一样带着链路框架去分析比背任何“十条优化建议”都管用。我自己这些年用 MySQL 最大的体会是数据库本身的机制并不神秘链路上每一环都有明确的参数和状态去描述它。你越理解这条链路越会在问题出现时感到踏实因为你知道自己手里的工具该用在哪一环。希望这篇把 MySQL 从连接数据库到查询全过程拆开的文章也能让读到这里的你在下次面对一个“奇怪”的数据库问题时先想到链路而不是先想到重启。
返回列表