前段时间碰到个奇怪的索引失效的问题,实际情况类似下面这样: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 bill=# begin; BEGIN bill=*# create index idx_t1 on t1(id); CREATE INDEX bill=*# explain select * f
前段时间碰到个奇怪的索引失效的问题,实际情况类似下面这样:
很显然的问题就是,我在事务中创建了索引,却没办法使用。但是当事务提交了后便可以正常使用了,这是什么情况呢? 这个其实和pg_index中indcheckxmin属性有关,关于这个字段的解释如下:
经检查也确实如此:
那么问题来了,什么情况下创建索引时会将索引的该属性设置为true呢? 从前面官方文档对于该字段的解释,如果表中包含broken HOT chains 则会为true,那什么是broken HOT chains ?似乎和HOT机制有关。那是不是只有存在broken HOT chains 才会设置为true呢? 这里就不卖关子了,直接给出结论,然后我们再去一一验证。 经测试发现,以下两种情况会导致索引的indcheckxmin设置为true:
场景一:broken HOT chains 这种情况,只要在当前事务中表中存在HOT更新的行时就会存在。那么什么时候会进行HOT更新呢?两个前提:
既然如此,实际中常见的两种情况就是:
例子: 表中插入10条数据,自然只有1个page:
进行更新:
查看发现的确是HOT更新: 关于t_infomask2字段的解释这里就不再赘述。 接下来我们创建索引: 可以发现indcheckxmin被设置为true,在当前事务中索引不可用。 经过验证,在index_build阶段,判断到BrokenHotChain,便将indcheckxmin修改为true。 具体的修改代码如下:
同样我们也可以验证得知,的确是因为brokenhotchains导致的indcheckxmin被设置为true。 场景二:old_snapshot_threshold 先来看例子: 最简单的场景,完全的一张空表,在事务中创建索引indcheckxmin就会被设置为true,果然索引也是不可用。
那么为什么old_snapshot_threshold会产生这样的影响呢? 经过跟踪发现,当开启该参数时,在事务中创建索引的snapshotdata结构如下:
而禁用该参数呢?
可以看到,区别在于不使用该参数时,创建snapshotdata不会设置whenTaken和lsn,那么这两个参数是干嘛的呢? 先来看看snapshotdata的结构:
如上所示,TimestampTz表示snapshot何时产生的,为什么启用old_snapshot_threshold时会设置该值呢? 因为该值正是用来判断快照是否过旧的:
这样我们也比较好理解为什么设置了该参数时创建的索引在当前事务中不可用: 因为我们不设置该参数时,在事务中创建索引是可以保证MVCC的一致性,那么索引便是安全可用的。 而使用参数时,由于TimestampTz被设置,数据库会对其进行判断该行数据是否已经过期,如果过期了那便会被清理掉,这样对于索引来说便是不安全的,没法保证数据的一致性,对于不是hot-safe的索引,自然要将其indcheckxmin设置为true,防止在事务中创建索引后数据实际已经过期被删除的情况。
总结 当pg_index的indcheckxmin字段被设置为true时,直到此pg_index行的xmin低于查询的TransactionXmin视界之前,查询都不能使用此索引。 而产生这种现象主要有两种情况: 1. 表上在当前事务中存在broken HOT chains; 2. old_snapshot_threshold被设置时。 |
2022-02-26
2022-09-22
2022-09-18
2022-09-22
2022-08-26