MySQL索引优化实战:从慢查询到高效索引设计
去年年底帮一个业务团队排查线上数据库抖动问题不复杂但很典型某张几千万行的订单表一个列表查询接口从平均80毫秒慢慢涨到了1.2秒数据库CPU一度冲到90%。慢SQL捞出来一看全表扫描连索引都没走到。这种问题在MySQL日常运维里太常见了翻来覆去核心就一句话——索引没打造好。很多开发同学对索引的理解停留在给WHERE条件的字段加个索引就行但实际做优化时会遇到一连串问题为什么加了索引还是慢为什么明明建了联合索引执行计划却走了全表扫描为什么索引建了一大堆写入性能反而直线下降这篇文章会用实际调优的思路把MySQL高效优化索引这件事从头到尾捋一遍先讲索引提速的底层原理再讲如何挑选字段、如何设计联合索引然后结合SQL写法和EXPLAIN诊断最后聊聊索引维护和冗余治理。内容兼顾原理和实操适合后端开发、DBA、以及所有被慢查询折磨过的同学。1. 一个查询慢的背后索引为什么能让扫描量差几个数量级1.1 没有索引时数据库在做什么——全表扫描的真实代价先做一个简单的估算。假设一张表有1000万行每行数据平均占用200字节那么全表数据大约是2GB。如果你执行一条不带索引的查询SELECT * FROM order_info WHERE user_id 123456;MySQL只能从数据文件的第一行开始逐行读入内存逐行判断user_id是否等于123456直到把整张表扫完。这个过程包含两个问题第一扫描的行数巨大。1000万行全部参与过滤假设符合条件的有1万行无效扫描比例是99.9%。第二IO成本极高。InnoDB是按页读数据的一页默认16KB2GB数据大约要读13万个页。如果是机械硬盘一次随机IO延迟大约10ms光是磁盘读取就能把人急死即使是SSD大量随机读也会显著拖慢响应。用生活里的场景类比全表扫描相当于你在一个没有目录的图书馆里找一本书只能从第一排书架开始一本一本翻索引则相当于图书馆的检索系统按“书名首字母”或者“作者编号”先定位到某一个书架再把范围缩小到某几本书。前者是费时费力的大海捞针后者是顺着目录逐层缩小范围。所以索引优化的第一个原则很简单尽量让查询从“扫描全表”变成“扫描索引树”把扫描范围从千万行缩到几十行或几百行。1.2 B树是把“大海捞针”变成“顺藤摸瓜”的功臣MySQL的InnoDB引擎使用B树作为索引结构。B树的核心特点可以归纳为三点数据只存放在叶子节点非叶子节点只存放索引键和指针。叶子节点之间通过双向链表连接天然有序。树的高度很低。千万级数据量下B树的高度通常只有3到4层。这意味着什么如果你通过主键或者唯一索引查找一条记录最多只需要走3到4次IO就能定位到数据页。对比全表扫描动辄几十万次页读取差距是几个数量级的。这里要区分InnoDB里面的两种索引聚簇索引Clustered IndexInnoDB中表数据本身就是按照主键构建的B树组织的。你插入的每一行完整记录都存储在聚簇索引的叶子节点上。如果没有主键InnoDB会选一个非空唯一索引替代没有的话它会隐式创建一个6字节的row_id作为主键。二级索引Secondary Index也叫辅助索引就是我们平时用CREATE INDEX创建的索引。二级索引的叶子节点不存整行数据而是存索引列的值 主键值。查询时先通过二级索引找到主键再根据主键去聚簇索引里拿整行数据这个动作叫做“回表”。为什么设计成二级索引只存主键而不存整行数据核心是为了节省空间和维持一致性。如果二级索引直接复制整行数据每建一个索引就等于存了一份完整数据副本空间浪费会非常大数据更新时还可能出现大量不一致。1.3 聚簇索引与二级索引为什么“回表”值得关注回表是理解索引优化绕不开的概念。举个例子SELECT * FROM user WHERE phone 13800138000;执行流程是这样的在phone字段的二级索引B树里找到phone 13800138000对应的叶子节点得到主键id。拿着主键id回到聚簇索引B树中重新走一遍树查找得到完整行数据。MySQL优化器通常认为回表代价不小**每回表一次等于多做一次主键查询的IO。**如果符合条件的数据有1万行就要回表1万次返回给客户端的可能只需要前20条分页限制之前的9980次回表全是浪费。所以索引优化里有一个非常重要的技巧叫覆盖索引Covering Index如果查询需要的所有列都包含在某个二级索引中那么MySQL就不需要回表直接在索引树里就能拿到全部数据。这个技巧后面会专门展开讲。回到开头那个场景为什么那条WHERE user_id 123456走了全表扫描原因无非两种——要么user_id根本没有索引要么有索引但因为SQL写法问题导致MySQL无法使用。这两种情况恰恰对应了接下来两节要解决的问题。2. 建索引前先想清楚哪些字段真正值得进索引列表2.1 从查询模式倒推字段等值、排序、分组三把尺决定在一张表上建哪些索引不能拍脑袋也不要照搬每个字段都建一遍的做法。我见过最夸张的一张表有30多个字段索引建了20多个结果写入慢到扛不住。正确思路是从实际查询模式倒推字段。具体操作很简单把业务系统里高频、慢查询相关的SQL都捞出来然后看每一条SQL的这三部分WHERE子句哪些字段参与了过滤ORDER BY / GROUP BY哪些字段参与了排序、分组JOIN ON哪些字段参与了表关联三部分综合起来就能得出这张表上的高频查询列。比如一个订单列表页最常见的查询是这三条SELECT * FROM order_info WHERE user_id ? AND status ? ORDER BY created_at DESC LIMIT 20; SELECT * FROM order_info WHERE order_no ?; SELECT COUNT(*) FROM order_info WHERE status ? AND created_at BETWEEN ? AND ?;那么候选索引列就很清晰user_id、status、created_at、order_no。下一步就是判断这些列如何组合、如何排序这就是联合索引设计的问题。这里有个非常容易犯的错误等值条件字段和范围条件字段的处理优先级不同。**等值条件、IN字段放在联合索引前面范围条件BETWEEN、、字段放在后面。**原因在于等值条件可以精确定位到索引树的某个区间而范围条件一旦出现即使后面还有排序字段索引也无法继续用于精确定位了。这条规则后面会结合最左前缀原则再演示一遍。2.2 区分度用可选择性判断一个列值不值得建有些字段虽然高频出现在WHERE里但未必适合建索引。关键看区分度。区分度Cardinality / Selectivity的估算公式很简单SELECT COUNT(DISTINCT user_id) / COUNT(*) FROM order_info;比值越接近1说明这个字段的取值越分散过滤效果越好比值越接近0说明大量行共享相同的取值建了索引也很难缩小范围。举个极端例子gender字段只有两个值可选区分度是2除以总行数接近0。如果查询WHERE gender male即使走了索引还是可能扫出一半的行优化器一算还不如全表扫描干脆直接放弃索引。而order_no这类业务唯一字段区分度接近1就非常适合建索引。所以一个字段是否值得建索引各字段的组合权重可以这样排序判断维度优先级说明查询频率高只有高频查询才值得付出索引维护成本区分度高区分度低索引带来的过滤收益很小更新频率低频繁更新的字段做索引写放大更明显字段长度短字段过长索引页能容纳的键数量变少树更高IO更多这个表格很适合贴在工位上每次纠结要不要为一个字段建索引就对照着打一遍分。2.3 联合索引的列顺序最左前缀原则的实际推演联合索引设计是MySQL索引优化里最核心、也最容易踩坑的部分。先背熟最左前缀原则联合索引(a, b, c)可以匹配(a)、(a, b)、(a, b, c)三种查询前缀但查询条件里如果没有第一个列a索引基本用不上如果跳过了b直接查cc的匹配能力也会大打折扣。为什么会有这个原则这得从B树的排序方式理解。联合索引的键值是按定义列的顺序拼接排序的先按a排序a相同再按b排序b相同再按c排序。就像电话号码簿先按姓氏排序同姓再按名字排序。如果你只知道名字不知道姓氏就没法按电话簿快速翻到那一页。用一个实际推演说明。业务查询是SELECT * FROM order_info WHERE user_id ? AND status IN (1, 2) ORDER BY created_at DESC LIMIT 20;如果我们建联合索引(status, user_id, created_at)查询条件里的第一个字段是user_id与索引第一列status不匹配MySQL根本不会用这个索引做精确定位只能走全表扫描或者碰运气做索引扫描完全浪费了设计。正确做法是设计成(user_id, status, created_at)user_id ?精确定位到某个用户的所有订单。status IN (1, 2)在user_id定位后的区间里继续用status过滤。ORDER BY created_at DESC在索引内部created_at已经有序可以直接按逆序遍历避免排序操作。这里有一个值得记住的细节联合索引(a, b, c)中只要b是一个范围条件比如BETWEEN、c通常就无法继续用于精确定位了但c仍然可能用于覆盖索引或者避免回表。3. SQL写法与索引失效能让索引“白建”的十种典型写法3.1 函数运算与隐式类型转换索引列被“加工”之后建了索引但用不上最常见的元凶之一就是对索引列做函数运算或隐式类型转换。先看函数运算。假设created_at上建了索引下面这条SQL在日期字段上套了DATE()函数SELECT * FROM order_info WHERE DATE(created_at) 2025-06-01;这条SQL的问题在于MySQL必须对每一行的created_at值都执行一次DATE()计算才能拿去和2025-06-01比较。索引树里存的原始值是没法直接用来匹配函数的输出结果的因为MySQL不知道函数输出的值与索引键之间有什么大小对应关系只能放弃索引老老实实全表扫描。正确写法是把范围条件化SELECT * FROM order_info WHERE created_at 2025-06-01 00:00:00 AND created_at 2025-06-02 00:00:00;这样created_at就能直接参与索引范围扫描。再看隐式类型转换。假设phone字段是VARCHAR(20)并且建了索引但查询时写了数字SELECT * FROM user WHERE phone 13800138000;MySQL内部会尝试把VARCHAR列转换成数字再比较等于对索引列做了一次隐性CAST索引照样失效。解决方案很简单参数类型必须和字段类型保持一致查询条件写成phone 13800138000。这类问题排查起来也容易核心就是一条原则别让索引列参与任何形式的计算无论是显式的函数还是隐式的类型转换、字符集转换、排序规则转换。3.2 范围查询、模糊匹配与OR这三个场景最容易被忽略范围查询导致索引失效是很多刚从“给字段加索引”阶段进阶的同学会踩的坑。假设联合索引是(status, created_at)查询SELECT * FROM order_info WHERE status 1 AND created_at BETWEEN 2025-06-01 AND 2025-06-30;status 1是等值条件走索引没问题created_at BETWEEN是范围条件在联合索引里status定位之后created_at可以做范围扫描。所以这个查询整体上能用到索引。但如果把顺序反过来SELECT * FROM order_info WHERE created_at BETWEEN 2025-06-01 AND 2025-06-30 AND status 1;表面上条件一样MySQL优化器一般会自动调整顺序不会因为这个顺序变换导致索引失效但如果索引设计是(created_at, status)当范围条件created_at在前、等值条件status在后时status就无法在索引内部进一步精确定位了。这里我想强调的是设计联合索引时把范围列放在等值列之后这是建索引之前就要定好的策略不能指望SQL写法和优化器来迁就你。模糊匹配是另一个经典坑。LIKE abc%能用索引因为MySQL可以把它当范围查询处理但LIKE %abc%因为通配符在最前面MySQL不知道前缀是什么只能全表扫描。同理LIKE %abc也基本不能用索引。OR条件则有一个隐藏限制如果两边有一边没有索引整个OR查询都可能放弃索引。例如SELECT * FROM user WHERE name 张三 OR phone 13800138000;当name有索引而phone没有索引时MySQL要走索引查name又要全表扫phone两边结果还要合并成本比全表扫描还高干脆直接选择全表扫描。解决思路要么两边都加索引要么改成UNION写法。这里还涉及优化器的成本估算不同版本可能表现不一致但“两边都有索引”这一点是通用前提。3.3 覆盖索引让查询连回表都省掉的设计思路前面提到过回表的代价。覆盖索引的设计目标就是让回表干脆不发生查询所需的列全部包含在某个二级索引中。举个例子。order_info表有联合索引(user_id, status, created_at)业务侧某条SQL只需要统计某个用户某状态下的订单数和金额总额SELECT user_id, status, COUNT(*), SUM(amount) FROM order_info WHERE user_id 123456 AND status 1;这个查询只需要读user_id、status两个索引列COUNT和SUM是聚合计算不需要原始行的其他字段那么MySQL可以在二级索引里直接把结果算出来完全不用回表。执行计划的Extra列会显示Using index。覆盖索引的实用价值在于它往往比分页还重要。比如列表分页经常这样写SELECT id, order_no, amount FROM order_info WHERE user_id 123456 AND status 1 ORDER BY created_at DESC LIMIT 10;如果联合索引设计为(user_id, status, created_at)查询需要的order_no、amount字段不在索引里所以MySQL只能用索引定位到所有符合条件的行可能有几千行然后逐一回表取数据再排序之后取前10条。几千次回表虽然比全表扫描好很多但其实还有优化空间改成用覆盖索引先查主键id再回表取整行。具体手法是先查询覆盖索引上的主键再用主键关联原表SELECT t.id, t.order_no, t.amount FROM ( SELECT id FROM order_info WHERE user_id 123456 AND status 1 ORDER BY created_at DESC LIMIT 10 ) AS tmp JOIN order_info t ON tmp.id t.id;内层子查询只访问索引树不碰数据行速度极快外层用10个主键回表回表次数被限制到10次。这个技巧在数据量大、回表成本高的场景下效果非常明显。4. 用EXPLAIN给慢SQL做体检关键列怎么读4.1 从哪里捞慢SQL慢查询日志与performance_schema优化索引的第一步是找到真正需要优化的SQL。我一般按照下面这个顺序排查开启慢查询日志把执行时间超过阈值的SQL记录下来。在MySQL 8.0中SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; SET GLOBAL log_output TABLE;然后把log_output设为TABLE慢SQL会记录到mysql.slow_log表里查询起来比解析日志文件方便很多。如果不想让所有慢SQL都落日志可以用performance_schema的events_statements_summary_by_digest表按SQL模板分组的统计信息里SUM_TIMER_WAIT、AVG_TIMER_WAIT、COUNT_STAR这些字段直接告诉你哪些SQL是最大黑手。拿到慢SQL之后二话不说先跑一遍EXPLAIN。这里需要提醒慢日志只能反映“单次执行慢”的SQL有些SQL单次可能只有几十毫秒但被调用了上万次累积耗时反而最夸张。这类高频SQL也要重点盯。4.2 type、key、rows、Extra四个关键信号逐个解读EXPLAIN输出列很多真正做索引优化时我主要看四列。先看type它是访问类型的核心指标从好到差大致是type含义是否理想system表中只有一行系统表极端情况const通过主键或唯一索引等值查询最多返回一行理想eq_ref连接查询时被驱动表按主键或唯一索引访问理想ref非唯一索引等值扫描可能返回多行良好range索引范围扫描例如、、BETWEEN、IN良好index全索引扫描遍历整棵索引树一般all全表扫描不理想如果type是all基本宣告这条SQL没用好索引。如果type是index虽然走了索引树但本质还是横扫整棵树数据量大时同样危险。再看key实际使用的索引名。如果为NULL说明这条查询没有用任何索引。这里要区分possible_keys可能被优化器考虑的索引和key最终选中的索引。有时候优化器会放弃某个明显可用的索引原因通常是它估算成本后发现全表扫描更快或者因为回表太多导致成本更高。第三看rows优化器预估需要扫描的行数。它不是精确值但方向性很强——rows越小通常查询越好。你可以通过强制索引的方式对比验证某条SQL指定的索引是否真的比默认选择更优。第四看Extra这一列信息量很大常见值包括Extra值含义Using index覆盖索引无需回表理想状态Using where索引定位后再对数据行做过滤Using filesort需要文件排序通常意味着ORDER BY没走索引Using temporary需要临时表GROUP BY或DISTINCT可能有问题Using index condition使用了索引下推ICP二级索引条件下推优化Using filesort特别值得注意。只要排序字段没有完全包含在索引中MySQL就可能为排序结果单独创建排序文件数据量一大这个排序操作可能比查询本身还慢。所以看到Using filesort第一反应是寻找能不能通过改造索引让排序直接走索引顺序。4.3 一个完整案例从全表扫描到索引扫描的改造过程分享一个我实际处理过的简化案例。表结构虚构简化如下CREATE TABLE order_info ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, order_no VARCHAR(32) NOT NULL, status TINYINT NOT NULL DEFAULT 0, amount DECIMAL(10,2) NOT NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), KEY idx_status (status) ) ENGINEInnoDB;待优化的业务查询是SELECT id, order_no, amount FROM order_info WHERE user_id 123456 AND status 1 ORDER BY created_at DESC LIMIT 10;执行EXPLAIN的结果是type: all一张几千万行的表全表扫描key: NULLrows: 上千万行Extra: Using where; Using filesort原因很清楚idx_status虽然存在但status区分度很低优化器认为扫全表比回表更划算而user_id和created_at根本没有索引可用。优化动作分两步走第一步新建联合索引ALTER TABLE order_info ADD INDEX idx_user_status_created (user_id, status, created_at);第二步重新执行EXPLAINtype: refkey: idx_user_status_createdrows: 预估几十行Extra: 无ORDER BY直接走索引顺序filesort消失再跑一次实际查询耗时从原来的800多毫秒降到20毫秒左右。整个优化过程中SQL一行没改只加了索引查询性能提升40倍。这就是索引设计正确带来的直观收益。5. 索引不是越多越好冗余索引与写入代价的取舍5.1 冗余索引是怎么悄悄长出来的前缀重复的联合索引很多团队的表结构经过几轮迭代后索引会疯狂膨胀。最常见的模式是一开始给user_id建了单列索引后来业务复杂了又建了(user_id, status)联合索引再去排查时发现user_id的单列索引其实是多余的。为什么说它多余因为联合索引(user_id, status)的最左前缀就是user_id它完全可以覆盖原来单列索引(user_id)的查询场景。保留了单列索引不仅白白浪费存储空间还会让INSERT / UPDATE的维护成本翻倍。类似的冗余还有(a, b)与(a, b, c)的组合、(a, b)与(a)的组合。判断规则可以记住一句话索引B的字段集合是索引A的最左前缀那么B是冗余的。至于查询INFORMATION_SCHEMA.STATISTICS表可以按表统计索引长度、索引列等人工核对冗余索引。MySQL 8.0还开放了sys.schema_redundant_indexes视图直接帮你把检测结果列出来非常方便。5.2 每次INSERT/UPDATE背后发生了什么写入放大索引能加快查询但代价是写入变慢。这个“代价”很多人没有量化感知我拆开讲讲。以InnoDB为例插入一行数据要做的事包括写入聚簇索引即表数据本身。如果有二级索引每一条二级索引都要插入一条索引记录。插入过程中如果对应索引页已经满了还要触发页分裂Page Split分裂会更新父节点指针可能继续向上蔓延。修改一行时所有包含被修改字段的索引都要同步更新如果索引键本身变了还涉及索引记录的删除和插入。简单估算一张表有5个二级索引插入一行数据实际上要做大约6次索引写入操作1次聚簇 5次二级同时在极端情况下还会伴随页分裂带来的额外IO。对应的写入延迟就是每多一个索引插入性能多被拖一分。我见过一个极端案例某配置表每行数据只有几十字节结果上面挂了7个索引单次INSERT的耗时居然比全表扫描还高。后来删掉了3个冗余索引写入性能直接翻倍。所以设计索引时一定要反问自己这个索引带来的查询收益是否值得为每次写入付出额外的维护成本尤其是写多读少的表索引策略宁可保守。5.3 索引维护的常规动作碎片整理与不可见索引索引用久了会产生碎片。删除大量数据后索引页可能存在大量空闲空间页利用率下降导致索引变大、扫描成本变高。这时可以通过重建索引或者ALTER TABLE ... ALGORITHMINPLACE来整理ALTER TABLE order_info DROP INDEX idx_user_status_created; ALTER TABLE order_info ADD INDEX idx_user_status_created (user_id, status, created_at);MySQL 8.0支持原子DDL这个操作在业务低峰期执行比较安全但要注意表体积大时即使INPLACE算法也可能产生大量日志和IO。这里推荐一个8.0非常好用的功能INVISIBLE INDEX不可见索引。它允许你把索引标记为“对优化器不可见”但索引本身仍然存在且持续维护。你可以通过它安全地验证“如果删掉这个冗余索引查询是否会变慢”ALTER TABLE order_info ALTER INDEX idx_user_status_created INVISIBLE;此时旧查询的EXPLAIN会显示无法使用这个索引。如果业务查询表现正常说明该索引可以正式删除如果某个查询开始全表扫描、性能明显下滑马上把它改回VISIBLE即可。这个验证过程不需要删除、重建索引是索引治理的利器。6. 一张表索引设计的checklist与几个容易忽视的坑6.1 从零设计一张订单表的索引完整推演结合前面的内容这里重新梳理一遍“从业务SQL出发设计索引”的完整流程。假设这是一张新的订单表虚构简化CREATE TABLE order_info ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, order_no VARCHAR(32) NOT NULL, status TINYINT NOT NULL DEFAULT 0, amount DECIMAL(10,2) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB;业务侧高频查询有以下几条用户订单列表WHERE user_id ? ORDER BY created_at DESC LIMIT 20;用户某状态订单WHERE user_id ? AND status ? ORDER BY created_at DESC LIMIT 20;订单号查询详情WHERE order_no ?;按状态统计某时间段订单量WHERE status ? AND created_at BETWEEN ? AND ?;逐个分析查询1和2是典型的“等值排序”组合适合建立(user_id, status, created_at)联合索引。注意查询1没有status条件但根据最左前缀原则user_id在联合索引最前面所以这条SQL也能用上索引只是少了一个等值条件过滤范围略大。查询3是单点等值order_no区分度极高建立唯一索引ALTER TABLE order_info ADD UNIQUE KEY uk_order_no (order_no);查询4的status区分度较低单独建status索引可能不被优化器采纳但如果created_at参与范围查询并且我们需要按时间统计会考虑在created_at上建索引。不过真实业务中这种统计查询通常走离线数仓在线库不一定需要专门优化。最终索引列表PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_status_created (user_id, status, created_at)这个方案有三个特点没有多余的单列索引唯一索引覆盖订单号查询联合索引同时服务列表查询和排序避免filesort。6.2 三个容易忽视的坑第一个坑**前缀索引的长度选择。**对于超长VARCHAR字段比如VARCHAR(255)甚至更长的文本字段直接建整列索引会让索引页能容纳的键数量急剧减少树变高IO变多。可以使用前缀索引ALTER TABLE user ADD KEY idx_user_nickname (nickname(10));但注意前缀索引不能用于覆盖索引也不能用于order by排序因为索引里只存了前缀不是完整值。适用场景是“等值过滤比回表成本更低”的大字段。第二个坑字符串排序的“意外”。如果业务上需要按某个VARCHAR字段做ORDER BY默认排序规则是字符串字典序按字符编码排序不是数字大小序。比如订单号是数字字符串按字典序排序和按数值排序的结果是完全不同的。如果业务要求按数值排序要么把字段类型改成整型要么在排序时ORDER BY CAST(order_no AS UNSIGNED)但后者又会导致索引失效。最好的方案是从数据设计层面让字段类型与业务语义匹配。第三个坑**NULL值与索引走不走的问题。**MySQL的索引是可以包含NULL值的IS NULL条件也走索引。很多人被“索引不会存NULL”的旧说法误导其实关键不在于NULL本身而在于查询写法。唯一索引允许存在多个NULL值这是标准SQL行为。如果业务上要求“某个手机号只能注册一次”把phone建成唯一索引时要意识到多行NULL不会被唯一约束限制。6.3 一个收尾的实操建议每次优化完索引后建议养成两个习惯第一保存优化前后的EXPLAIN对比和耗时对比。不需要很复杂一张随手记录的表格即可。下次再遇到类似慢查询可以直接翻出历史方案省去重新分析的时间。第二用ANALYZE TABLE刷新统计信息。索引优化后如果发现优化器依然“执拗”地选择全表扫描很可能是因为统计信息过旧。执行一下ANALYZE TABLE order_info;大多数时候优化器会重新给出正确的执行计划。我在实际调优中最大的体会是索引优化不是一个“建完就完事”的动作而是一个持续迭代的过程。业务查询模式变了数据分布变了统计信息变了索引策略就要跟着调整。与其追求“一步到位”不如建立一套从慢SQL发现、EXPLAIN诊断、索引设计、冗余清理的闭环机制。把这套流程跑顺了MySQL的查询性能问题大部分都可以在应用层面解决而不是动不动就扩容加机器。

相关新闻

图像质量评价经典算法代码复现:BRISQUE、RankIQA与NIMA实战指南

图像质量评价经典算法代码复现:BRISQUE、RankIQA与NIMA实战指南

简介:这份资源面向图像处理方向的学习者与开发者,系统整理了图像质量评价领域的经典算法代码实现,涵盖BRISQUE、RankIQA、NIMA等代表性方法,可用于监控视频传输质量评估、图像压缩与增强算法效果验证、医疗影像分析等需要量化图像…

2026/10/17 3:51:57 阅读更多 →
Docker部署Elasticsearch:从启动命令到稳定运行的完整指南

Docker部署Elasticsearch:从启动命令到稳定运行的完整指南

你如果只在网上搜docker run -d -p 9200:9200 elasticsearch,照着一帖,大概率能启动,但能不能用就是另一回事了。我见过太多同事卡在“容器起来了但日志疯狂报错”“elastic账号密码是什么”这类问题上,还有更糟的——起来之后一写…

2026/10/17 3:51:57 阅读更多 →
TraceVIC:用因果推理定位漏洞诱导型提交

TraceVIC:用因果推理定位漏洞诱导型提交

1. 这不是又一个“git blame”工具——TraceVIC到底在解决什么真问题?你有没有遇到过这样的场景:线上服务突然爆出一个高危漏洞,安全团队火速拉群,SRE盯着告警面板手心冒汗,开发组长翻着最近三天的提交记录头皮发麻。大…

2026/10/17 3:50:57 阅读更多 →

最新新闻

Cocos Creator拖拽排序列表实现:坐标转换、Tween动画与数据持久化

Cocos Creator拖拽排序列表实现:坐标转换、Tween动画与数据持久化

简介:这是一份基于Cocos Creator实现的拖拽排序列表Demo资源包,面向需要构建可交互排序界面的游戏客户端或应用开发者。资源以拖拽移动、排序存储、状态更新三大核心功能为主线,通过监听触摸与鼠标事件捕获拖拽的开始、移动和结束状态&#x…

2026/10/17 4:39:12 阅读更多 →
吉布斯采样实现LDA主题模型:MATLAB代码实战与避坑指南

吉布斯采样实现LDA主题模型:MATLAB代码实战与避坑指南

简介:面向机器学习和自然语言处理方向开发者的开源实现,基于吉布斯采样方法完成LDA主题模型推断,帮助解决高维概率分布采样的核心难题,适用于文本挖掘、主题建模等场景,也适合作为MCMC算法入门的代码范本。资源共15个文…

2026/10/17 4:39:12 阅读更多 →
MES系统如何打通车间现场:从黑盒到透明,制造执行的核心价值解析

MES系统如何打通车间现场:从黑盒到透明,制造执行的核心价值解析

1. 从车间现场的“黑匣子”说起:MES到底在解决什么问题先说一个我在制造业圈子里反复听到的抱怨:很多企业的老板和管理者,花大价钱上了ERP,觉得“系统都有了,管理应该没问题了”,结果到了车间一看&#xff…

2026/10/17 4:39:12 阅读更多 →
Linux最小化系统搭建Firefox与VNC远程桌面完整指南

Linux最小化系统搭建Firefox与VNC远程桌面完整指南

一台用最小化模式装好的 Linux,装完就是纯命令行环境,连图形界面都没有。但很多时候,你又确实需要在这台机器上跑出一个有界面的浏览器——用 Firefox 登录网页后台、远程给同事演示页面效果,或者把一台旧电脑改成专用网页终端。于…

2026/10/17 4:39:12 阅读更多 →
神经网络六十年:从感知机到深度学习的演进之路

神经网络六十年:从感知机到深度学习的演进之路

神经网络这门技术,说白了是“熬”出来的。从感知机到深度神经网络,名字听着像是直线升级,实际上中间经历了被主流抛弃、被同行嘲笑、被资金断供的至暗时刻,又靠着几代研究者不服输的执念和几次偶然的算力突破,才一步步…

2026/10/17 4:39:12 阅读更多 →
AI Agent从概念到落地:核心模块拆解与实战避坑指南

AI Agent从概念到落地:核心模块拆解与实战避坑指南

1. 别把AI Agent当成“更聪明的聊天框”这两年AI圈最火的概念,大概就是“AI Agent”了。和ChatGPT刚出来时大家热衷于“调戏”它写诗、编段子、生成周报不同,现在的焦点已经明显转变了:我们不再满足于让大模型“说话”,而是希望它…

2026/10/17 4:38:12 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →