带过滤与排序的大表查询如何选择合适的多列B树索引
核心问题根因
你当前使用的(last_updation_date, entity_id)联合B树索引无法适配查询的核心原因是:该索引优先按last_updation_date排序,仅相同时间值内的行按entity_id排序,符合时间范围的所有行的entity_id是全局无序的。数据库无法复用索引的有序性直接完成order by entity_id逻辑,只能先捞出所有符合时间条件的行再做全量排序,当时间范围覆盖的数据量较大时,排序成本远高于全表扫描成本,优化器会直接放弃使用该索引。
适配场景的优化方案
方案1:调整索引结构(适合小limit、数据分布均匀的场景)
创建(entity_id, last_updation_date)联合B树索引:
create index i_entityid_upddate_idx on entity using btree (entity_id, last_updation_date);
该索引全局按entity_id排序,数据库可以按entity_id顺序遍历索引,过滤符合时间条件的行,直到凑够limit指定的行数即可返回,完全避免排序操作。如果你的查询limit值通常在数千以内,且符合时间范围的行在全表中分布相对均匀,该方案的性能提升最为显著。
方案2:SQL语句改写(无需调整现有索引,适合大时间范围查询场景)
如果你不想调整现有索引,可以将查询拆分为子查询+主键关联的形式,最大化利用现有索引的能力:
select * from entity e inner join ( select entity_id from entity where last_updation_date between <START-VALUE-1> and <END-VALUE> order by entity_id limit <XYZ> offset <ABC> ) t on e.entity_id = t.entity_id order by e.entity_id;
内层子查询仅需要查询entity_id和last_updation_date两个字段,可以直接走你已创建的索引完成索引仅扫描,不需要回表,仅对小体积的entity_id字段做排序,成本极低,再通过主键关联获取完整行数据,性能远高于原始写法。
额外优化建议(大offset场景通用)
如果你的查询offset值经常超过1万,建议放弃offset分页改用Keyset分页(Seek分页):记录上一页返回的最后一个entity_id值,下一次查询新增entity_id > 上一页最后ID的条件,去掉offset参数,可以完全避免跳过大量无效行的开销,分页性能提升10~100倍。
内容的提问来源于stack exchange,提问作者Amar Malik

