首页/新闻资讯/正文详情

MySQL索引底层原理与慢SQL优化实战:从B+树到联合索引设计

发布时间:2026/9/24 20:04:19 来源:云帆数科 栏目:资讯中心
MySQL索引底层原理与慢SQL优化实战:从B+树到联合索引设计
从一次慢查询说起吧。有个同事做报表导出一条SQL跑了快3秒数据量也就几十万行按理说不该这么慢。我先让他把SELECT的WHERE条件列出来再一看表结构——好家伙整整十来个字段里面连一个索引都没建。加了两个联合索引之后同样的查询直接掉到40毫秒上下报表接口的P99延迟降了一个数量级。那次之后我越来越觉得MySQL索引这东西懂的人觉得简单但真要在生产环境里设计好、排查对细节里的门道是真不少。这篇文章就把我对MySQL索引的理解完整梳理一遍从数据结构到实操设计再到故障排查一次讲透。适合谁看刚入门想搞懂索引原理的开发者写SQL经常莫名慢、不知道加什么索引的后端以及面试前想系统过一遍索引知识点的求职者。能解决什么问题搞清楚索引为什么快、什么时候该建索引、建什么样的索引、索引为什么会失效、以及拿到一条慢SQL之后怎么定位和优化。1. 索引的本质与底层数据结构1.1 索引到底是什么索引的本质是一种排好序的数据结构它的存在意义只有一个——减少磁盘IO次数让查询更快。你可以把索引理解为新华字典的“偏旁部首检字表”。字典正文是按拼音排的但你想按部首找一个字的时候不会一页页翻而是先去检字表里找到这个字在第几页然后再翻到那一页去拿正文。索引就是这张“检字表”它告诉你数据在磁盘的哪个位置省去了全表扫描的代价。MySQL的索引是在存储引擎层实现的不是MySQL服务层统一实现。所以不同存储引擎InnoDB、MyISAM、Memory等的索引结构并不一样用法上也有差异。目前主流生产环境几乎都在用InnoDB所以后面讲的内容默认都以InnoDB为准。1.2 为什么MySQL默认选择B树索引的数据结构候选有很多哈希表、二叉树、红黑树、B树、B树。MySQL最终选择了B树这不是随意的而是每个候选都有明显短板。哈希表单条等值查询确实快到极致O(1)复杂度。但哈希索引天生不支持范围查询、、BETWEEN也不支持排序联合索引的多个字段也无法部分匹配。再加上哈希碰撞处理会引入不确定性InnoDB只在自适应哈希索引Adaptive Hash Index场景下把B树索引优化成哈希结构不能作为通用索引方案。二叉树极端情况下会退化成链表比如按递增主键插入树的高度直接等于节点数量查询退化到O(N)。红黑树虽然通过自平衡把高度控制在O(logN)但它是二叉结构树高度依然太高。数据量大到千万级别时红黑树高度在30到40层之间。如果每一层的节点都要做一次磁盘IO那就是30到40次IO延迟不可接受。B树多路平衡查找树一个节点能存多个键值树的高度被大幅压缩。但B树的节点既存索引键也存数据或者数据指针每个节点能容纳的键数量有限相同数据量下树还是偏“胖”。而且B树的中序遍历比较复杂做范围查询时需要回溯到父节点效率不如B树。B树在B树基础上做了两个关键优化。第一是数据只存在叶子节点非叶子节点只存索引键这样单个节点能容纳的键数量更多树更矮第二是叶子节点之间用双向链表连接范围查询、排序、分组都能通过顺序遍历叶子链表完成不需要像B树那样回溯。以一个3层B树为例估算一下InnoDB一页默认16KB假设主键是BIGINT8字节加上6字节的行指针每页大约能存16 * 1024 / 14 ≈ 1170个索引键。第二层同样的结构第三层是叶子节点假设每行数据1KB那么叶子页能存16行。算下来一棵3层B树能支撑约1170 * 1170 * 16 ≈ 2000万行数据。也就是说在2000万行规模下从根节点查到一个叶子节点只需要3次磁盘IO这就是B树强悍的地方。1.3 InnoDB聚簇索引的物理存储结构InnoDB的索引是**聚簇索引Clustered Index**组织方式它指的是表的数据行本身就存在主键索引的B树的叶子节点上。这里有个关键点InnoDB表不是“数据文件 索引文件”的分离结构而是数据文件本身就是主键索引构成的B树。叶子节点上存放的是完整的行记录包括所有字段。这就是为什么InnoDB表必须有主键——没有主键数据就没法按照有序结构存储。如果你建表时没有指定主键InnoDB会用第一个非空唯一索引作为主键如果连唯一索引都没有InnoDB会生成一个隐藏的6字节ROW_ID作为聚簇索引。这个隐藏主键你平时感知不到但它占存储空间也影响写入性能所以我一直建议每张表都应当显式定义主键。除了主键聚簇索引其他索引普通索引、联合索引、唯一索引等都叫二级索引Secondary Index。二级索引的叶子节点存储的内容不是完整行数据而是索引列的值 对应主键值。举个例子一张表有如下结构CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL, age INT DEFAULT NULL, PRIMARY KEY (id), KEY idx_name (name) ) ENGINEInnoDB;主键索引 idx_primaryB树叶子节点存的就是整行数据即(id, name, age)全部字段按id排序。二级索引 idx_nameB树叶子节点存的是(name, id)按name排序。当你执行SELECT * FROM user WHERE name 张三时MySQL会先走二级索引idx_name找到name张三的叶子节点取出对应的主键id然后再用这个id去主键聚簇索引上查一次完整行。这个过程叫做回表Bookmark Lookup。如果你的查询只需要name和id两个字段那么二级索引里就已经有全部需要的数据不需要回表这种可以“仅凭索引就满足查询”的场景叫覆盖索引Covering Index。2. MySQL索引的类型与适用场景2.1 按功能分类MySQL索引按功能维度可以分为四个基础类型索引类型特点说明典型使用场景普通索引INDEX/KEY仅加速查询不约束数据唯一性高频WHERE条件过滤字段唯一索引UNIQUE KEY加速查询 保证列值唯一用户手机号、身份证号、订单号主键索引PRIMARY KEY特殊的唯一索引且不允许为NULL每一张InnoDB表必备全文索引FULLTEXT基于分词匹配支持模糊检索长文本内容的关键字搜索一个非常容易踩的坑是唯一索引不是越多越好。唯一约束的维护需要额外索引结构每次插入、更新都要做唯一性检查写入性能会受影响。如果某个字段只是业务上天然唯一比如用户邮箱但你其实不依赖数据库来强制唯一就建普通索引即可没必要用UNIQUE。全文索引也容易误解。它和LIKE %关键字%完全不是一回事全文索引是全文检索技术在InnoDB中基于分词倒排实现查询语法是SELECT * FROM article WHERE MATCH(title, content) AGAINST (数据库 IN BOOLEAN MODE);但说实话生产环境的全文搜索我一般不推荐用MySQL自带的FULLTEXT。数据量大了以后全文索引的维护成本很高性能也不如专业的Elasticsearch或专门搜索引擎稳定。MySQL全文索引更适合做轻量级搜索或者数据量可控的场景。2.2 按存储结构分类聚簇索引与非聚簇索引前面提到过InnoDB是聚簇索引组织方式数据行存在主键索引B树的叶子节点上。MyISAM则不同它是非聚簇索引——数据文件和索引文件独立存储索引B树的叶子节点存的不是数据行而是一个数据行的物理地址指针无论主键索引还是二级索引都指向这个地址。两者的优缺点非常明显对比维度InnoDB聚簇索引MyISAM非聚簇索引数据存储数据在聚簇索引叶子节点数据在独立文件索引存物理地址查询效率主键查询不需要回表主键查询也需要按地址取数据插入顺序按主键顺序插入效率高与主键顺序无强关联随机主键插入可能导致页分裂性能下降影响相对较小二级索引叶子存主键值可能回表叶子存数据地址不回表但数据文件分散如今MySQL 8.0中MyISAM已经不太推荐使用了你可以通过理解MyISAM的非聚簇存储模式来对照理解InnoDB的设计。2.3 联合索引复合索引联合索引是在多个字段上建立的索引它是实际工作中设计难度最高、收益也最大的一种索引类型。它的核心特点是遵循最左前缀原则Leftmost Prefixing。B树的多列索引会先按照第一个索引字段排序字段相同再按第二个字段排序以此类推。所以MySQL使用联合索引时只有查询条件从最左边开始连续匹配索引列才能命中索引。CREATE TABLE order ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, status TINYINT NOT NULL, create_time DATETIME NOT NULL, PRIMARY KEY (id), KEY idx_user_status_time (user_id, status, create_time) ) ENGINEInnoDB;对上面这个联合索引(user_id, status, create_time)WHERE user_id 100命中最左列user_id匹配。WHERE user_id 100 AND status 1命中。WHERE user_id 100 AND status 1 AND create_time 2024-01-01命中。WHERE status 1不命中因为跳过了最左列user_id。WHERE user_id 100 AND create_time 2024-01-01部分命中user_id能用到索引但create_time在范围查询中无法继续使用索引排序只能回表用user_id过滤后的结果再筛选。联合索引设计时的一条核心思路是把等值查询条件放在最前面范围查询条件放在最后面。因为联合索引一旦遇到范围查询、、BETWEEN后面的索引列就全部失效了等于做不了索引排序和过滤。2.4 索引设计的两大原则实际设计索引时我会反复用两个指标衡量一个索引是否合理cardinality基数和选择性Selectivity。基数Cardinality索引列上不同值的数量。基数值越大说明列能区分度越高。选择性基数 / 表总行数选择性越接近1索引效果越好。在MySQL里你可以直接查看索引基数值SHOW INDEX FROM user;注意这个值在MySQL 8.0之前是统计估算的不是精确值数据变化后可能需要ANALYZE TABLE来更新统计信息。如果发现某个索引的cardinality和实际数据严重不符优化器就可能会误判放弃索引。举例一张100万行的用户表sex字段只有“男/女”两个值选择性是2 / 1000000 0.0002%。如果你在sex上建索引查询WHERE sex 男会返回约50万行MySQL优化器一看这个返回值占总数据量比例太高大概率会直接放弃索引、走全表扫描。这不是索引没用而是这种“低区分度”字段本身就不适合单独建索引。真正有效的索引字段选择性应当尽量高通常是主键、手机号、订单编号、身份证号这样的高唯一性字段。3. 索引的创建、分析与实战设计3.1 索引的创建方式MySQL创建索引有几种途径不同场景我用不同的方式建表时直接定义索引适合表结构比较稳定、从一开始就明确的索引。CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, phone VARCHAR(20) NOT NULL, name VARCHAR(50) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone), KEY idx_name (name) ) ENGINEInnoDB;ALTER TABLE添加索引适合给已存在的表加索引生产环境最常用。ALTER TABLE user ADD INDEX idx_name (name); ALTER TABLE user ADD UNIQUE KEY uk_phone (phone);CREATE INDEX创建索引功能等价于ALTER TABLE语义上更直观。CREATE INDEX idx_name ON user (name); CREATE UNIQUE INDEX uk_phone ON user (phone);删除索引DROP INDEX idx_name ON user;查看表上的索引SHOW INDEX FROM user;3.2 三种常见方案对比很多同学纠结用哪种方式建索引其实手段上区别不大核心区别在“要不要先评估”。方式优点缺点适用场景建表时定义表结构清晰索引随表创建表结构变更不灵活新表上线ALTER TABLE可运行时变更灵活大表加锁风险日常线上加索引CREATE INDEX语义清晰与ALTER TABLE本质相同开发者手动加索引线上给大表加索引要注意的是MySQL 8.0之前的版本ALTER TABLE会导致锁表影响线上写入。如果你在几十万甚至上百万行的大表上直接执行加索引可能在执行期间阻塞读写。我建议优先考虑使用在线DDLInnoDB支持ALGORITHMINPLACE或者通过工具如gh-ost、pt-online-schema-change来平滑加索引避免长时间阻塞线上业务。3.3 索引设计三步法我在设计索引时的通用思考路径可以总结成三步第一步梳理核心查询路径。把业务里的高频查询SQL全部列出来尤其是WHERE子句里的等值条件、范围条件、排序字段、分组字段。比如电商订单库的核心查询是SELECT * FROM order WHERE user_id 123 AND status 1 ORDER BY create_time DESC LIMIT 20;那(user_id, status, create_time)就是一个顺理成章的联合索引候选。第二步判断每个字段的区分度。如果字段选择性太低比如status只有几个离散值那它就不适合放索引的最前位置只适合作为联合索引中间或后面的过滤条件。第三步分析是否让查询走向覆盖索引。如果一条查询频繁执行而且高频查询返回的字段集合固定比如业务只需要订单号和状态那就可以创建一个(user_id, status, order_no)的覆盖索引让查询完全不用回表。这个收益在大并发查询下非常可观。3.4 最容易忽略的排序与分组场景索引不仅用于WHERE过滤也用于ORDER BY和GROUP BY。SELECT user_id, COUNT(*) FROM order WHERE create_time BETWEEN 2024-01-01 AND 2024-06-30 GROUP BY user_id;这条SQL如果没有合适索引MySQL会把符合时间条件的所有行捞出来再做一个临时表和文件排序filesort执行效率很低。如果建一个(create_time, user_id)联合索引因为B树天然有序MySQL可以顺序扫描索引并直接完成分组统计大大减少临时表开销。从执行计划上能看到区别走临时表的执行计划Extra字段会出现Using temporary; Using filesort而走索引消除排序时Extra字段没有这个提示。4. 索引失效的十大典型场景与避坑指南4.1 对索引列使用函数或表达式计算这是最经典的索引失效场景。假设你在create_time上建了索引-- 索引失效 SELECT * FROM user WHERE DATE(create_time) 2024-01-01; -- 索引生效 SELECT * FROM user WHERE create_time 2024-01-01 00:00:00 AND create_time 2024-01-02 00:00:00;原因很简单B树里存放的是原始列值MySQL只能对原始值做范围匹配。一旦给索引列套了一个函数或计算表达式优化器就无法直接利用有序结构去定位只能对全表每个值都算一遍再过滤索引自然失效了。核心原则是索引列保持纯洁不做任何运算。我之前排查过一条慢SQLWHERE条件里写了WHERE DATEDIFF(NOW(), pay_time) 7pay_time上明明有索引却一直全表扫描。改成pay_time DATE_SUB(NOW(), INTERVAL 7 DAY)之后查询从13秒降到了0.2秒。4.2 隐式类型转换MySQL在字段类型不匹配的时候会自动做隐式类型转换。问题在于一旦类型转换发生在索引列上索引就失效了。最常见的情况是手机号字段是VARCHAR类型但代码里传给SQL的参数是INT。-- phone是varchar类型 SELECT * FROM user WHERE phone 13800138000;MySQL会把phone列转成数字再比较等于对索引列执行了CAST操作索引失效。解决办法很简单参数类型与字段类型保持一致。SELECT * FROM user WHERE phone 13800138000;4.3 LIKE前置通配符LIKE ‘abc%’可以利用索引因为MySQL可以在B树上按前缀做范围扫描但LIKE ‘%abc%’和LIKE ‘%abc’无法利用索引因为要匹配的字符串可能出现在字段任意位置无法从有序的B树直接定位。如果业务确实需要模糊搜索有几个替代方案前缀搜索固定前缀比如只搜首字母开头的编码对于中缀或后缀模糊匹配考虑用全文索引适合大文本、较长内容如果是小场景、数据量有限可以接受全表扫描但加好LIMIT。4.4 OR连接非索引字段OR在MySQL优化器里是个“捣乱分子”。如果OR的两个条件里有一个字段没有索引那么即便另一个字段有索引优化器也只能放弃索引走全表扫描。原因在于全表扫描可以一次性把OR两边的行都捞出来如果用索引则要分别扫描再合并优化器在代价估算后往往不会选择索引。-- 假设user_id有索引phone没有索引 SELECT * FROM user WHERE user_id 100 OR phone 13800138000;改进策略给phone也加上索引或者改写成UNION ALLSELECT * FROM user WHERE user_id 100 UNION ALL SELECT * FROM user WHERE phone 13800138000;4.5 NOT IN、NOT EXISTS、!、操作符不等判断通常是范围扫描无法精确定位的场景。MySQL一般会认为NOT IN需要读取大部分数据直接走全表扫描性价比更高。只有当表数据量小、或者过滤比例很高时优化器才有可能反选走索引。这类场景下我建议改成等值或范围查询的组合或者直接用应用层过滤。特别是NOT IN里子查询的结果集很大的时候性能往往灾难。4.6 联合索引不满足最左前缀这个前面详细讲过了再重复强调一次联合索引(a, b, c)查询条件必须包含a且不能跳过中间列。常见误区例子-- idx_a_b_c 联合索引以下查询全部无法完整使用索引 WHERE b 1 WHERE c 1 AND b 1 WHERE a 1 AND c 1 -- a可以用c用不到注意MySQL 8.0的**索引跳跃扫描Index Skip Scan**在某些场景下可以让跳过最左列的查询也使用索引但它有严格的前提条件表不大、优化器认为值得生产环境依赖它不现实。还是按最左前缀原则来设计查询和索引更稳妥。4.7 优化器认为全表扫描更快不是所有带索引的查询MySQL都会用索引。优化器会基于表的行数、索引基数、数据分布等统计信息估算代价如果它认为全表扫描代价更低就会放弃索引。这通常发生在数据量极小的表低区分度字段被查询比如sex数据分布极不均匀比如status 1的行占了90%。这种情况下即使索引没“失效”实际执行计划也确实没走。修正方式是提高索引区分度或者调整查询条件让过滤比例降低。4.8 索引失效场景速查表操作是否可用索引说明索引列上使用函数/表达式失效禁止对索引列做运算隐式类型转换失效参数类型保持与字段一致LIKE abc%可用前缀匹配可用LIKE %abc%失效中缀/后缀无法定位OR连接非索引列失效两边都建索引或用UNION拆分NOT IN / ! / 一般失效优化器倾向全表扫描联合索引跳过最左列失效遵守最左前缀低区分度字段单独查可能失效优化器估算代价高5. 用EXPLAIN定位索引问题的实操方法5.1 EXPLAIN关键字段解读排查慢SQL的第一板斧就是看执行计划。MySQL提供了EXPLAIN命令通过它可以看到一条SQL实际会怎么执行。EXPLAIN SELECT * FROM order WHERE user_id 100 AND status 1 ORDER BY create_time DESC LIMIT 20;输出结果里有几个关键列我逐个说明type访问类型从好到差依次是system const eq_ref ref range index ALL。如果type是ALL说明全表扫描必须要警惕。出现system/const说明查询走的唯一索引或主键等值查询是最理想状态。ref表示非唯一索引等值查询range表示范围扫描这些都是合理的访问类型。key实际用到的索引名。如果key为NULL说明没走索引。rows预估扫描行数优化器会基于这个值做代价估算rows越小越好。Extra常见值包括Using index覆盖索引、Using where在存储引擎层过滤、Using temporary使用了临时表通常伴随group by/order by性能问题、Using filesort文件排序如果数据量大往往需要优化。最理想的Extra是Using index代表查询直接从索引中获取全部数据没有回表。而Using temporary; Using filesort基本可以认为是性能信号灯需要关注。5.2 一个慢SQL的完整优化过程分享一个实际案例。某后台管理系统的订单列表页按商户ID和时间筛选订单SQL是SELECT * FROM order WHERE merchant_id 2001 AND create_time 2024-03-01 ORDER BY create_time DESC LIMIT 20;执行计划显示type ALLkey为NULLrows 200万。因为order表是200万行的大表且商户ID在业务上不是高区分度字段一个商户可能几十万单单独给merchant_id建索引效果一般。我最终建的索引是ALTER TABLE order ADD INDEX idx_merchant_time (merchant_id, create_time);索引设计逻辑merchant_id等值条件放前面create_time做范围过滤和排序放后面。联合索引天然有序ORDER BY create_time不再需要额外排序一次索引扫描就能完成。优化后执行计划type refkey idx_merchant_timerows 213Extra Using index condition。接口响应时间从1.8秒降到了35毫秒左右。这个案例说明单列索引不一定能解决排序过滤的组合需求联合索引往往才是正解。5.3 覆盖索引的极致性能之前讲回表的时候提过覆盖索引这里看一个实战应用。在订单表上高频查询是统计某用户某时间段内各状态的订单数SELECT status, COUNT(*) FROM order WHERE user_id 3001 AND create_time BETWEEN 2024-01-01 AND 2024-06-01 GROUP BY status;联合索引(user_id, create_time, status)可以把这条SQL完全覆盖通过user_id等值定位、create_time范围扫描、status直接看看叶子节点上的索引值即可完成分组统计全程不需要回表拿整行数据Extra展示为Using index。对于大并发高频繁的统计场景这个优化的收益是百毫秒到微秒级别的差异。6. 索引常见问题与面试高频题总结6.1 面试高频问题一问一答结合这些年的面试经历MySQL索引有几个问题几乎必问我把常见问题和清晰的答案整理一下。为什么InnoDB表必须有主键因为InnoDB聚簇索引的叶子节点存储的是整行数据。如果没有主键InnoDB会用第一个非空唯一索引做主键再没有就生成隐藏的ROW_ID。隐式主键用户不可见也无法利用它做高效的查询。显示定义主键可以选择最适合业务查询的聚簇索引顺序对写入和查询最有利。主键用自增ID还是UUID这个问题的核心是“主键的有序性”。自增ID按顺序递增插入B树时总是追加到最右边不会触发随机的页分裂UUID是随机字符串主键索引的有序性被打破插入时会导致大量随机位置的页分裂、数据页碎片化性能会明显差于自增ID。如果业务需要分布式环境生成主键可以改用雪花ID之类的有序ID而不是UUID。为什么索引能让查询变快但还会让写入变慢因为索引是一种额外的数据结构。每次INSERT/UPDATE/DELETE除了要改数据行还要维护所有相关索引的B树结构比如插入新键时可能需要页分裂、更新时可能需要移动索引项。所以索引越多写入维护成本越高。设计原则是在满足查询需求的前提下索引“少而精”避免冗余索引。联合索引字段顺序怎么定核心思路等值查询字段放在最前范围查询字段放最后区分度高的放前面经常ORDER BY/GROUP BY的字段也尽量利用索引顺序。具体再结合业务实际流量做取舍。覆盖索引和回表是什么关系二级索引叶子节点存的是“索引列 主键值”。查询时如果需要的字段都在二级索引里就不需要拿主键去聚簇索引再查一次这叫覆盖索引。如果SELECT的字段里有不在二级索引里的列就必须用主键回表回表次数越多性能越差大分页查询通常就慢在这里。6.2 常见问题的原因与处理建议现象可能原因处理建议查询单行很慢无索引或索引失效EXPLAIN查看执行计划加了索引还是不生效函数运算、隐式转换、OR连接按第4节逐项排查索引基数统计严重偏差长时间未更新统计信息ANALYZE TABLE大分页慢LIMIT 100000, 20需要大量回表延迟关联或游标分页写多读少表越来越慢索引冗余过多删除无效索引合并联合索引排序慢缺少覆盖排序字段的索引联合索引中放ORDER BY字段6.3 几个实用的避坑经验第一给大表加索引要评估锁表风险。MySQL 8.0之前在线DDL也不是完全无锁。给大表加索引前建议先看表数据量评估执行时间用gh-ost这类工具做平滑变更避免在业务高峰期操作。第二不要盲目相信索引越多越好。一个常见的坏习惯是一个查询条件一个索引结果表上堆了几十个索引。实际上联合索引可以覆盖多个查询索引数量应该维持在个位数到十几个以内每个索引都要有明确的收益依据。第三写完SQL后用EXPLAIN习惯性过一遍。这可能是投入产出比最高的习惯了。不管是新写完的SQL还是接手别人的SQL先EXPLAIN一眼看type、key、rows、Extra四个字段基本就能判断这条SQL会不会在数据量大之后出问题。第四理解优化器不等于完全相信优化器。MySQL的优化器是基于统计信息的统计信息不准时会作出错误判断。如果确认索引没问题但优化器没走可以用FORCE INDEX临时指定但优先还是要弄清楚优化器为什么没选索引比如统计信息过旧、区分度不够。7. 写在最后的实践体会做了这么多年开发和数据库优化我对索引最深的体会有三点。第一索引是“空间换时间”的典型它巧妙地利用B树的有序特性把随机磁盘IO转换成顺序IO让千万级数据量下的查询依然能保持毫秒级延迟。凡是要加速查询第一反应不是调参而是看索引设计是否合理。第二索引设计一定是结合业务查询模式做的脱离SQL谈索引没有意义。一张表的索引不是“越多越好”而是“越准越好”。多花一点时间梳理业务的高频查询路径一本万利。第三排查慢SQL时EXPLAIN的四个字段——type、key、rows、Extra——基本能判断80%的问题。养成写SQL就跑执行计划的习惯比记一堆“索引失效口诀”有用得多。如果你现在正被一条慢SQL困扰先把SQL丢进EXPLAIN里看一眼往往答案就已经浮出水面了。

相关推荐

Linux下MySQL 8.0从安装到配置调优全指南:避开仓库、认证与远程连接的坑
Linux下MySQL 8.0从安装到配置调优全指南:避开仓库、认证与远程连接的坑

Linux上装MySQL 8.0,命令搜出来一大堆,但真正从零亲手装过的人都知道,坑基本都在命令之外的细节里。仓库源没配好用系统默认源拉回来一个旧版本,初始化完找不到临时密码,远程用Navicat一连直接报认证插件不兼容&#x… · 2026/9/24 20:04:19

MySQL增删改查实战:从建库建表到SQL优化与常见报错排查
MySQL增删改查实战:从建库建表到SQL优化与常见报错排查

1. 先把MySQL跑起来:连接、建库、建表的基本功接触MySQL增删改查之前,我发现很多人其实卡在最前面的几步——装好了MySQL,却不知道怎么连上去,也不知道建表时该设什么字段类型。这篇文章不打算讲那些高大上的架构设计,… · 2026/9/24 20:04:13

Electron 迁移 Tauri 实战:安装包从 224MB 压到 4.7MB
Electron 迁移 Tauri 实战:安装包从 224MB 压到 4.7MB

跨平台桌面应用这块,Electron 长期是默认答案,但它带来的体积和内存代价,做过打包的人心里都有数。一个再普通不过的 Vue 项目,套上 Electron 之后安装包动辄两百多兆,装完占几百兆磁盘,冷启动还要等 Chrom… · 2026/9/24 20:04:13

ARIMAX工业时序建模实战:外生变量对齐、滞后阶数选择与边缘部署
ARIMAX工业时序建模实战:外生变量对齐、滞后阶数选择与边缘部署

简介:本资源是一套基于ARIMAX(自回归积分滑动平均外生变量)模型的多变量时间序列预测完整实现,面向数据分析、量化建模及机器学习初学者与实践者,适用于经济指标、销售趋势、气象参数等含外部影响因子的预测场景。压缩… · 2026/9/24 20:46:51

YOLOv5 6.1全中文注释版:从源码解析到树莓派部署实战
YOLOv5 6.1全中文注释版:从源码解析到树莓派部署实战

简介:YOLOV5 6.1版本全中文注释源码包,面向目标检测初学者、研究生及创新创业大赛参赛团队,针对官方代码结构复杂、英文注释难以理解等痛点,对模型构建、数据集准备、训练验证、推理部署等核心模块逐行添加中文注解,并… · 2026/9/24 20:46:45

SpringBoot+Vue墙绘交易平台:从订单设计到并发控制的全栈实战解析
SpringBoot+Vue墙绘交易平台:从订单设计到并发控制的全栈实战解析

我直接说结论:如果你现在想找一个既能练手、又能直接拿去生产环境的Java全栈项目,基于SpringBootVue的墙绘产品展示交易平台,是个相当合适的参考系。这个项目把电商交易、内容展示、后台管理三个核心场景串在一起,技术栈又恰好是当… · 2026/9/24 20:46:45

图转PPT技术解析:从OCR到PPTX的完整实现路径
图转PPT技术解析:从OCR到PPTX的完整实现路径

1. 为什么“一键生成PPT”这件事,远没有想象中简单1.1 从一句需求说起:AI生成PPT到底卡在哪“用AI一键生成PPT”这个说法,这两年几乎成了办公效率赛道的标配口号。你在任何一个内容平台搜“AI做PPT”,都能看到大量演示视频&#x… · 2026/9/24 20:46:45

Qt QPainter二维绘制从原理到实战:机制、坐标系与仪表盘实现
Qt QPainter二维绘制从原理到实战:机制、坐标系与仪表盘实现

在Qt开发里,画图这件事十有八九绕不开QPainter。无论是做自绘控件、数据可视化面板,还是临时画个折线图、仪表盘、地图标注,最终都要落到这个类上。很多人觉得QPainter难,其实是没把它的绘图机制、坐标体系和常用API串起来理解。这… · 2026/9/24 20:46:45

图片转PPT全链路实战:OCR、版面分析与PPTX生成避坑指南
图片转PPT全链路实战:OCR、版面分析与PPTX生成避坑指南

图片转PPT这件事,表面上看是个格式转换的小需求,但真正动手做过的人都知道,坑远比想象中多。我最初接触这个需求,是因为手头有一批纸质培训资料和扫描版的技术文档,需要整理成可编辑的PPT课件。当时想得很简单——图片… · 2026/9/24 20:46:45

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码