最近遇到一个sql 优化问题,现场反馈 一张50万数据的表创建索引后根据条件查询只返回2行数据,但是执行计划显示并没有走索引,尝试了各种方式比如hint、强制关闭seqscan等操作,都不起作用。
一、无法使用索引的场景
1. 开启 old_snapshot_threshold 参数
alter system set old_snapshot_threshold=1;2. 开启两个会话,验证无法使用索引的场景
| 会话一 | 会话二 |
|---|---|
test=# begin; | |
test=# create index idx_t2 on t2(id); | |
| commit; | |
test=# explain select * from t2 where id=12; |
可以发现,当存在比创建索引事务更早的活动事务时,在活动事务结束前,新创建的索引是无法使用的。
3. 查看pg_index.indcheckxmin
test=# select xmin,indisvalid,indcheckxmin,indisready,indislive from pg_index where indexrelid = 'idx_t2'::regclass;
xmin | indisvalid | indcheckxmin | indisready | indislive
--------+------------+--------------+------------+-----------
101644 | t | t | t | t
可以看到 pg_index.indcheckxmin 为 true 。该字段的含义如下:
在此索引对应的 sys_index 行的 xmin 小于当前所有活动事务 snapshot 的 backend_xmin 数值之前,任何查询都不能使用此索引。
二、old_snapshot_threshold 参数
Oracle 有个 undo_retension 参数,表示事务修改操作undo信息保留的时长。当查询时长超过undo_retension值时,就会包“snapshot too old”错误。PostgreSQL old_snapshot_threshold 参数就相当于 Oracle undo_retension 参数。
PG 中是通过vacuum回收脏数据,也就是我们常说的dead tuple,但是有时候长事物会持有一些dead tuple很长时间,这样就不能及时做vacuum回收脏数据,就使得数据库中的表很容易膨胀,并占用额外的存储空间,还会导致相关SQL操作性能下降。所以为了避免以上结果,从9.6引入了old_snapshot_threshold参数。当old_snapshot_threshold默认值为-1,是关闭的。当我们设置该为具体时间的时候,vacuum 就不会等待长时间的查询结束,会继续对dead tuple进行处理,如果刚好有事物正好在使用这些dead tuple,那么就会报错snapshot too old。
三、为什么索引不可用?
1. backend_xid 和 backend_xmin
sys_stat_activity.backend_xid 表示已申请事务号的事务,例如有增删改,DDL等操作的事务。backend_xid 从申请事务号开始持续到事务结束。
sys_stat_activity. backend_xmin 表示SQL开始执行(也可能是事务开始时。视隔离级别而定)时的系统活动事务的xid最小值(snapshot),例如查询语句,查询游标。backend_xmin 从SQL开始持续到SQL结束,如果是游标的话,持续到游标关闭。
2. HOT 更新
KingbaseES当前的多版本的方案,是在update和delete元组时并不是直接的删除一行,而是通过xid和标记手段来设置这个元组对以后的事务不可见。对于update 操作,如果原数据块无法容纳“新”记录,也就是在其他块插入一条记录,那需要在索引插入一条entry。而如果The Heap Only Tuple (HOT) update,也就是原数据块可以容纳新记录,那索引无需新加一条entry,只需要表原数据块 slot 链接到新 slot 。
3. old_snapshot_threshold 引发 HOT 断链
由于 开启了old_snapshot_threshold 参数,即使在有活动事务的情况下 vacuum 操作也会进行,这会导致发生HOT 断链的情况,这时候HOT链中索引的键值已更改,但是为了保证数据的一致性,要保证键值发生变化前的事务就不能引用该索引。




