技术实践丨列存表并发更新时的锁等待问题原理
摘要:当开启 transaction,执行 updata 的语句执行成功,不执行 commit 或 rollback;再开启另一个窗口,执行 upadate 语句,会出现失败(报错:锁等待超时)的情况,但是如果对于上一个窗口执行 rollback,此窗口 update 可以执行成功,该种情况应考虑该表是否为列存表。
本文分享自华为云社区《列存表并发更新时时的锁等待问题原理》,原文作者:PFloyd。
当开启 transaction,执行 updata 的语句执行成功,不执行 commit 或 rollback;再开启另一个窗口,执行 upadate 语句,会出现失败(报错:锁等待超时)的情况,但是如果对于上一个窗口执行 rollback,此窗口 update 可以执行成功,该种情况应考虑该表是否为列存表。
【问题根因】
如果使用的是列存表,在事务中执行 update 操作时,是以 CU 为单位进行加锁的,所以在事务未提交时并发更新同一 CU 的其他数据时会出现锁等待的情况,等待超时的时候会出现报错。
【机制原理】
1. CU 为压缩单元(Compress Unit),列存储的最小单位,导入数据时生成,生成后数据固定不可更改,单个 CU 最多存储 1 列 60000 行数据。同一列的 CU 连续存储在一个文件中,当大于 1G 时,切换到新文件中。其中的 Ctid 字段标识列存表的一行,由 cu_id 和 CU 内行号(cu_id, offset)组成;一次性写入的多条的数据位于同一 CU。
2. 为了防止页面同一个元组被两个事务同时更新,在进行 update 时,都会加上行级锁,对于行存来说是对一行数据加锁,对于列存来说就是对一个 CU 加锁,当一个事务 update 未提交时,其他事务是无法同时去更新同一 CU 的数据的。
3. 进行 update 操作后,旧元组被标记为 deleted,新元组会写到一个新的 CU 中。
【案例分析】
1.根据现场的报错信息,可以确定是并发更新报错;进行 update 时,会申请行级锁,在申请行级锁之前会申请 transactionid 锁,等待超时后报错信息为:waiting forShareLock on transaction xxx after ..ms。
2. 客户反应更新的并不是同一条数据,id 不同,询问客户后得知出现问题的是列存表,查询更新的数据是否处于同一 CU。
查询后发现处于同一 CU,符合预期。
3. 本地场景复现:
起事务执行 update 操作:
事务未提交并发更新数据出现等待:
查询后发现两条数据位于同一 cu:
【相关问题】
为什么 update 成功一次之后,下一次 update 就不会互相等锁了?
这是因为 update 成功之后,旧数据被标记为 deleted,新数据写入新的 CU,这两条数据不再是同一个 CU 了,也就不存在这种锁冲突。
【处理方案】
列存表不适合频繁的 update 场景,列存频繁的 update 容易触发并发更新等锁超时,并且会导致小 CU 过多,而每个 CU 都会扩展至 8192 字节进行对齐,从而导致磁盘空间迅速膨胀;频繁的点查或频繁 update 场景建议使用行存表。
版权声明: 本文为 InfoQ 作者【华为云开发者社区】的原创文章。
原文链接:【http://xie.infoq.cn/article/830eed961a5db9226cc95e5fa】。文章转载请联系作者。
评论