很显然的问题就是,我在事务中创建了索引,却没办法使用。但是当事务提交了后便可以正常使用了,这是什么情况呢?
这个其实和pg_index中indcheckxmin属性有关,关于这个字段的解释如下:
If true, queries must not use the index until the xmin of this pg_index row is below their TransactionXmin event horizon, because the table may contain broken HOT chains with incompatible rows that they can see
经检查也确实如此:
那么问题来了,什么情况下创建索引时会将索引的该属性设置为true呢?
从前面官方文档对于该字段的解释,如果表中包含broken HOT chains 则会为true,那什么是broken HOT chains ?似乎和HOT机制有关。那是不是只有存在broken HOT chains 才会设置为true呢?
这里就不卖关子了,直接给出结论,然后我们再去一一验证。
经测试发现,以下两种情况会导致索引的indcheckxmin设置为true:
当前事务中表上存在broken HOT chains,即官方文档中所说;当old_snapshot_threshold被设置时。场景一:broken HOT chains
这种情况,只要在当前事务中表中存在HOT更新的行时就会存在。那么什么时候会进行HOT更新呢?两个前提:
新的元组和旧元组必须在同一个page中;索引字段不能进行更新。既然如此,实际中常见的两种情况就是:
对表上最后一个page进行更新;表设置了fillfactor,即每个page上有预留的空闲空间。例子:
表中插入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) $6 = { snapshot_type = SNAPSHOT_MVCC xmin = 856 xmax = 856 xip = 0x00007fd55c804fc0 xcnt = 0 subxip = 0x00007fd55ad5d000 subxcnt = 0 suboverflowed = false takenDuringRecovery = false copied = false curcid = 1 speculativeToken = 0 vistest = NULL active_count = 0 regd_count = 0 ph_node = { first_child = NULL next_sibling = NULL prev_or_parent = NULL } whenTaken = 691752041261069 lsn = 208079736}
而禁用该参数呢?
(SnapshotData) $7 = { snapshot_type = SNAPSHOT_MVCC xmin = 828 xmax = 828 xip = 0x00007fad31704780 xcnt = 0 subxip = 0x00007fad3155d000 subxcnt = 0 suboverflowed = false takenDuringRecovery = false copied = false curcid = 1 speculativeToken = 0 active_count = 0 regd_count = 0 ph_node = { first_child = NULL next_sibling = NULL prev_or_parent = NULL } whenTaken = 0 lsn = 0}
可以看到,区别在于不使用该参数时,创建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被设置时。
到此这篇关于PostgreSQL索引失效会发生什么的文章就介绍到这了,更多相关PostgreSQL索引失效内容请搜索七叶笔记以前的文章或继续浏览下面的相关文章希望大家以后多多支持七叶笔记!