大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
ORDER BY create_time DESC,create_time上有索引,EXPLAIN也显示用了索引,查询却比升序慢了一大截。
这个问题在MySQL 8.0之前几乎是必然的。索引定义里写DESC,MySQL静默忽略 ------实际存储仍是升序,ORDER BY ... DESC走的是反向索引扫描 。InnoDB页面内的记录用单向链表组织,每个记录头只存指向"下一条"的指针,没有"上一条"。正向遍历沿着指针走一步就行,O(1);反向遍历必须从页头扫到页尾才能找到前一条,代价是O(n)。
8.0引入降序索引,把DESC真正落实到物理存储层面:键值按降序排列,ORDER BY ... DESC可以正向扫描索引,效率与升序扫描持平。
但"建了降序索引"和"优化器真的用上了"之间,还隔着三个边界。
一、降序索引最大的价值不在单列,在混合排序
如果只是单列ORDER BY a DESC,升序索引的反向扫描虽然慢一点,但在小结果集上差异并不显著。降序索引真正不可替代的场景,是复合索引中不同列需要不同排序方向。
bash
CREATE INDEX idx_cust_total ON orders (customer_id ASC, total DESC);
对于ORDER BY customer_id ASC, total DESC,8.0之前的升序索引(两列都ASC)无法同时满足"第一列升序、第二列降序"的排序要求,只能走filesort。降序索引让索引的物理顺序与ORDER BY的排序方向逐列对齐 ,优化器可以直接正向扫描索引返回结果,消除filesort。
验证方法 :EXPLAIN的Extra列。建降序索引前是Using filesort,建对之后filesort消失,key列显示idx_cust_total。
二、边界一:MIN/MAX优化不适用于降序索引
这是官方文档中明确列出的限制,但很少被提及。
MySQL对SELECT MIN(col) FROM t(无GROUP BY)有一个优化:如果col上有升序索引,优化器直接读取索引的第一条记录就返回,O(1)。但降序索引不参与这个优化 ------如果索引是(col DESC),MIN(col)的优化不会被触发。
原因在于优化器的实现假设:MIN优化依赖于"升序索引的第一条就是最小值"。降序索引的第一条是最大值,逻辑不匹配,优化器选择保守地放弃这条路径。
实际影响 :如果业务中有大量MIN()查询,不要为了ORDER BY ... DESC在建降序索引的同时牺牲掉MIN的优化。需要评估两个查询模式的权重。
三、边界二:降序索引不支持Change Buffer
Change Buffer是InnoDB对非唯一二级索引写入的一种优化:当索引页不在Buffer Pool中时,变更先缓存在Change Buffer中,等页被读入时再合并,避免写入路径上的随机I/O。
官方文档明确:如果二级索引包含降序键列,Change Buffer不被支持。
这意味着:一张写密集的表,如果某个二级索引建成了降序,这个索引的写入路径上没有Change Buffer的缓冲 。对于随机写入模式(主键非自增或二级索引键分布分散),写入性能可能受到可见的影响。降序索引在读路径 上省掉的filesort开销,可能在写路径上被放大。
判断逻辑:读多写少的报表/查询表,降序索引的收益大概率覆盖代价。写密集的交易表,需要先做写入侧的压测,确认TPS下降在可接受范围内。
四、边界三:GROUP BY的隐式排序没了,别再依赖它
8.0之前,GROUP BY在某些情况下会隐式对结果进行排序,很多业务代码无意中依赖了这个行为,没有写ORDER BY。
8.0引入降序索引后,这个隐式排序不再保证 。如果业务依赖GROUP BY的输出顺序,必须显式加上ORDER BY,否则结果集的顺序可能变化。
这不是降序索引本身的缺陷,而是升级到8.0后需要主动排查的兼容性问题。降序索引只是让这个问题更容易被触发。
五、小结
降序索引在8.0中才真正生效。它的核心价值在混合排序 (ASC+DESC的复合索引),单列降序的收益相对有限。三个边界需要记住:MIN/MAX优化不适用、Change Buffer不支持、GROUP BY的隐式排序不再保证。建降序索引之前,先确认三件事:你的查询是混合排序吗?这张表是读密集还是写密集?有没有依赖GROUP BY隐式排序的存量代码? 三问都过了,再动手。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~