pg报错attempted to delete invisible tuple

news2024/9/24 13:14:16

问题描述

postgresql数据库执行delete报错:attempted to delete invisible tuple,执行同样条件的select不报错

delete from lzltab1;
select count(*) from  lzltab1;

执行全表删除和全表查询的结果:

M=# delete from lzltab1;
ERROR:  55000: attempted to delete invisible tuple
LOCATION:  heap_delete, heapam.c:2500
Time: 511.050 ms
M=#   select count(*) from  lzltab1;
 count  
--------
 231187

delete找到了不可见的元组,select却是正常的 。
当时觉得很奇怪。pg的可见性通过元组的xmin,xmax,cid和快照的xmin,xmax,xip_list判断的,虽然delete元组的事务状态和时间会对可见性产生影响,但是表数据如果稳定(当前没有任何dml操作的话),随后的任何快照进去拍摄,都应该是一个稳定的可见集,它并不存在当前事务可见性判断,dml事务元组可见性判断也应该一致。也就是说这种场景下,select快照和delete快照不应该有这种差异。

问题分析

找到源码

注意报错信息heapam.c:2500
找到源码位置src/backend/access/heap/heapam.c
2500的行是空的,附近的代码如下

	/*
	 * Before locking the buffer, pin the visibility map page if it appears to
	 * be necessary.  Since we haven't got the lock yet, someone else might be
	 * in the middle of changing this, so we'll need to recheck after we have
	 * the lock.
	 */

	if (PageIsAllVisible(page))
		visibilitymap_pin(relation, block, &vmbuffer);

	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);

从源码看,它在尝试获得vm上的锁,看来问题跟vm文件相关。

vm文件

什么是vm文件?
vm文件是为了减少vacuum扫描page时间的,如果一个page不需要vacuum那么它应该可以被vacuum跳过,这样可以大大减少vacuum寻找需要清理的page的时间,这是vm文件最初的目的。(有时候也会被index-only scan使用,不过我们这里不会涉及,我们用的是全表扫描)
vm文件包含两个信息:

  1. page中的元组是不是都是可见的。说明page中没有需要被vacuum的死元组
  2. page中的元组是不是都是冻结的。说明vacuum freeze不需要访问这个page

Fig. 6.2. How the VM is used.

vm会帮助vacuum寻找死元组,减少扫描的页的数量。比如上面这个图(interdb yyds!),第一个页面1st不包含死元组,那么vacuum就可以跳过这个页面。

找到vm文件
每个表都有Visibility Map (VM)(索引没有vm文件),单独存放在表文件的旁边,如果一个表的filenode为12345,那么vm文件应该是12345_vm
首先cd到data目录

M=# show data_directory;
    data_directory    
----------------------
 /pg/pg6666/data

通过database oid,表的oid可以找到文件存储的位置

=# select oid,datname from pg_database where datname='sdp';
-------+----------------------
  oid  |  datname
 17075 | sdp
=# select  oid,relname from pg_class where relname='lzltab1';     
-------+----------------------
 17362 | lzltab1

or

#  select pg_relation_filepath('lzltab1');
pg_relation_filepath 
----------------------
base/17075/17362

找到数据文件和vm

$ cd  /pg/pg6666/data/base/17075
$ ll 17362*
-rw------- 1 postgres postgres 86761472 Jun 15 17:43 17362
-rw------- 1 postgres postgres    40960 Jun  9 21:09 17362_fsm
-rw------- 1 postgres postgres     8192 Nov 14  2022 17362_vm

pg_visibility插件

pg_visibility提供通过检查vm文件输出页级别的可见性信息,而且可以检测vm是否损坏。因为vm存储了“page中的元组是不是都是可见的;page中的元组是不是都是冻结的”这些信息,pg_visibility可以检查出哪些页是all-frozen的,哪些是all-visible的。
pg_visibility插件使用参考:https://www.postgresql.org/docs/current/pgvisibility.html

会用到的pg_visibility中的function

pg_visibility_map_summary():显示vm中的all-visible页和all-frozen页
pg_check_frozen():有元组不是frozen的,但在它所在的页在vm中被标记为了all-frozen,如果该函数有返回,表示vm文件损坏。
pg_check_visible():有元组不是visible的,但在它所在的页在vm中被标记为了all-visible,如果该函数有返回,表示vm文件损坏。
pg_truncate_visibility_map():清理vm文件。清理vm后,当表首次执行vacuum时,会扫描表上的所有页并重建vm。

修复vm文件

检查vm是否损坏

M=# select pg_visibility_map_summary('lzltab1');
 pg_visibility_map_summary 
---------------------------
 (472,0)

all-visible 472页,all-frozen 0页

M=# select pg_check_frozen('lzltab1');
 pg_check_frozen 
-----------------
(0 rows)
M=#  select pg_check_visible('lzltab1');
 pg_check_visible 
------------------
 (6839,1)
 (6839,2)
 ...
  (7296,15)
(1423 rows)

pg_check_visible()有结果说明vm已经损坏了
然后用pg_truncate_visibility_map()执行清空vm

M=# select   pg_truncate_visibility_map('lzltab1');
 pg_truncate_visibility_map 
----------------------------

从磁盘上也能看出来vm被清空了

  ll 17362*
-rw------- 1 postgres postgres 86761472 Jun 27 10:39 17362
-rw------- 1 postgres postgres    40960 Jun  9 21:09 17362_fsm
-rw------- 1 postgres postgres        0 Jun 27 18:18 17362_vm

然后我们验证一下,vacuum表看下是否会生产vm文件,并检验vm文件是否没有损坏

M=# vacuum lzltab1;
VACUUM
Time: 3692.402 ms (00:03.692)
M=# \q
$ ll 17362*
-rw------- 1 postgres postgres 86761472 Jun 28 03:37 17362
-rw------- 1 postgres postgres    40960 Jun  9 21:09 17362_fsm
-rw------- 1 postgres postgres     8192 Jun 28 10:21 17362_vm

可以看到手动vacuum后vm正常生成

M=# select pg_check_visible('lzltab1');
 pg_check_visible 
------------------
(0 rows)

M=# select pg_check_frozen('lzltab1');
 pg_check_frozen 
-----------------
(0 rows)

check检查后没有输出,vm文件正常,修复完成。

最后再来跑下sql

# delete from lzltab1;
DELETE 229766

delete执行正常,问题解决

检查全库是否有vm损坏

虽然我们解决了一个vm文件损坏的问题,但是仍然需要全库检查是否有其他的vm损坏(前提是安装了extension pg_visibility)

SELECT oid::regclass AS relname
FROM pg_class
WHERE relkind IN ('r', 'm', 't') AND (
  EXISTS (SELECT * FROM pg_check_visible(oid))
  OR EXISTS (SELECT * FROM pg_check_frozen(oid)));

如果返回非空结果,说明有vm损坏了。就像上面的方法,用pg_truncate_visibility_map()清理vm,然后vacuum生成一个vm。
如果是9.6前的版本,因为没有pg_visibility插件,需要停库然后手动删除vm文件,再启动数据库,然后再vacuum生成一个vm。

为什么vm会损坏?

我们通过一步步分析,检查到是vm文件损坏了,但是vm为什么会损坏呢?

  1. pg数据库的bug。pg确实有些bug会导致vm损坏(参考wiki Visibility Map Problems),不过这些都是pg9.6.1以前的
  2. 操作系统和硬件问题

我们版本是pg13的,问题基本只能笼统地归于操作系统或者硬件问题。

为什么select没有问题,delete报错?

select全表正常,delete全表报错,看上去就很奇怪。问题的根因是vm文件的损坏。
就像前面说的,vm文件是为了加快vacuum效率的,我们虽然没有做vacuum,而vm文件总要更新的吧?dml每次都会去更新vm(至少要检查),而select不会改变vm状态的。所以本案例中select正常执行,delete在执行到vm的处理时报错。
我们的案例中,delete扫描了vm找到了all-visible的页,但是vm标记错了,这些页上仍然有不可见的元组,这里就对应了开头的报错attempted to delete invisible tuple。不可见的元组可能已经被delete了,再次跑delete当然会报错,这也违背了事务的可见性规则。
另外,如果是index-only-scan也会用到vm文件,所以也会影响select语句,不过这个案例是全表扫描的,所以select没有问题。

vm损坏导致index-only-scan出现错误的数据结果

前面在介绍vm的时候提到处理vacuum外,index-only-scan也会用到vm文件,虽然我们这个案例没有涉及index-only-scan,但本着研究透彻的原则,把问题搞清楚。

什么是index-only-scan

index-only-scan顾名思义就是仅索引扫描。在访问数据的时候不需要访问表,而只访问索引结构就能得到想要的结果。几乎所有的关系型数据库都有仅索引扫描,因为B+树索引结构保存了键值,如果查询只查了键值,那么仅索引扫描就是可能的。
但是,postgresql因为其事务实现跟其他数据库(Oracle、mysql)有很大的不同,它的仅索引扫描index-only-scan有一些特殊性。
postgresql通过元组头部的xmin、xmax等信息,校验元组和事务的可见性,而索引上没有这些信息,所以就导致pg的仅索引扫描必须访问数据块来检查可见性。这个时候vm的作用就体现了出来,因为vm文件中保存了all-visible,all-frozen的信息,这些被标记的页其实不需要校验元组可见性,因为他们已经被vm标记为可见了。
Fig. 7.7. How Index-Only Scans performs
再来一个interdb的图(interdb yyds!)。当一个sql查询在访问key是18和19两个元组时,key=18元组所在的页已经被vm标记为all-visible了,所以访问key=18的元组只需要访问索引页和vm文件;而key=19的元组所在的页没有被标记为all-visible,仅索引扫描还是要访问所在数据页获取元组可见信息。

index-only-scan查询出错误的结果

因为index-only-scan要访问vm,而vm损坏而保存了错误的信息,比如页中的元组本来不是所有都可见的(比如几个元组被delete了),但是页还是在vm中被标记为all-visible,导致index-only-scan没有进数据页检测元组可见性,直接返回了索引页上的本来是不可见的键值。
可以设置enable_indexonlyscan=off来关闭index-only-scan特性,保证数据的正确性;当然也可以像上面那样去修复vm文件,也许是更好的选择。

总结

虽然刚开始有些曲折,一看报错以为是事务可见性规则出现了问题,这个有问题的话问题就有点大了,但是实际上要简单的多。
我们从报错attempted to delete invisible tuple分析到源码,并定位到了是vm问题,再通过pg_visibility插件检测出vm corrupt并修复了vm文件,从而解决了delete报错,最后扩展了一下index-only-scan和vm。
总结一下文章的知识点:

  • pg_visibility插件可以读取、检测、清理vm文件
  • 如果没有vm信息的话,vacuum会生成新的vm
  • dml会读取/更新vm文件,select不会(非index-only-scan)
  • vm文件是为了提升vacuum的效率,有时候也会提升index-only-scan的效率
  • attempted to delete invisible tuple报错应该检查vm文件是否损坏
  • vm文件损坏会造成dml失败,也会造成index-only-scan查询出错误的结果

参考

https://www.postgresql.org/docs/13/pgvisibility.html
https://wiki.postgresql.org/wiki/Visibility_Map_Problems
https://www.interdb.jp/pg/pgsql06.html
https://www.interdb.jp/pg/pgsql07.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/696708.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有一个三位数,他的各个位数的阶乘相加得到这个数

有一个三位数,他的各个位数的阶乘相加得到这个数 1.描述 有一个三位数,它的各个位数的阶乘相加得到这个数 2.代码 输入数据 依次对个位,十位和百位进行拆解计算阶乘 然后相加看看是不是和原来的数据是相等 public class Mian4 {public static void main(String[…

ROS系列报错与解决方法

6.28 一、问题描述 ROS运行roscore命令后发现提示log文件(日志文件)大小超过1G,需要清理 Checking log directory for disk usage. This may take awhile. Press Ctrl-C to interrupt WARNING: disk usage in log directory [/home/wht/.ros/log] is over 1GB. Its recom…

红外线探测防盗报警器电路设计

该报警器能探测人体发出的红外线,当人进入报警器的监视区域内,即可发出报警声,适用于家庭、办公室、仓库、实验室等比较重要场合防盜报警。 一、电路工作原理 电路原理如图4所示。 该装置由红外线传感器、信号放大电路、电压比较器、延时…

这可能是最简单的Page Object库

做过web自动化测试的同学,对Page object设计模式应该不陌生。 Page object库应该根据以下目标开发: Page object应该易于使用 清晰的结构 PageObjects 对于页面对象 PageModules对于页面内容 只写测试,而不是基础。 在可能的情况下防止样…

【AI 充电】KServe + Fluid 加速大模型推理

作者:黄驰琳、露营、车漾 背景 KServe 是 Kubernetes 上的标准模型推理平台,专为高度可扩展的场景而构建,支持现代 Serverless 推理工作负载,用于在任意框架上提供机器学习(ML)模型服务。它提供高性能、高…

菜鸡shader:L5 fresnel、matcap和cubemap

文章目录 Fresnelshader forge实现UE4蓝图实现 Matcapshader forge实现UE4蓝图实现 CubeMapSD和PS制作所需的贴图shader forge实现unity代码实现UE4蓝图实现(未实现) Fresnel shader forge实现 个人理解是,使用观察方向和法向方向点乘,那就相当于我们的视…

adb-命令大全

目录 一、ADB简介 二、为什么要用ADB 三、ADB架构和原理 四、ADB日志状态 五、ADB常用命令 🎁更多干货 完整版文档下载方式: 一、ADB简介 ADB全称Android Debug Bridge,起到调试桥的作用,是一个客户端-服务端程序。其中客…

C++数据结构X篇_06_C++单向循环链表实现

本篇参考C单向循环链表实现整理,先搞懂结构框架,后期根据视频利用c对内容实现,也可以对c有更高的提升。 文章目录 1. 链表定义2. 链表插入3. 链表打印(注意打印过程是跳过头节点head的)4. 实验 单向循环链表与单向链表十分相似,具…

ST - NUCLEO-H723ZG ITM不生效的问题

文章目录 ST - NUCLEO-H723ZG ITM不生效的问题概述CubeMX配置点END ST - NUCLEO-H723ZG ITM不生效的问题 概述 在用NUCLEO-H723ZG官方板子做实验, 想用ITM打印一些调试信息出来. 不好使啊. 后来查问题, 发现 NUCLEO-H723ZG上的HSE, LSE的晶振无效, 等于是不能用外部的HSE, LS…

如何找到更多音视频开发学习资料和资源?

如果你对学习音视频开发感兴趣,以下是一些建议,可以帮助你获取更多相关的资料和资源: 在线学习平台:参考一些知名的在线学习平台,如Coursera、Udemy、edX等,搜索他们的课程目录,看是否有与音视频…

【SWAT水文模型】SWAT水文模型建立及应用第六期:SWAT模型率定(SWAT CUP)

SWAT模型率定(SWAT CUP) 1 准备工作2 水文模型率定(SWAT CUP)数据准备运行流程 参考 SWAT CUP的下载及安装参见另一博客-【SWAT水文模型】SWAT-CUP安装及使用 下面将具体介绍基于实测水文站数据进行水文模型的率定。 1 准备工作…

【Centos系统故障】虚拟机断电后centos7无法正常启动 XFS(sda3)

1. 背景: 虚拟机断电后centos7无法正常启动 XFS(sda3),通常都是断电以及非法关机,杀掉虚拟机等突然中断系统的情况,会导致内存数据损坏,从而导致linux操作系统无法启动。 2. 解决办法: 清理掉内存数据&a…

windows10 安装wsl2+docker+php+nginx+mysql

第一步在windows10 上安装docker(可视化桌面) 第二步确定本地电脑开启虚拟化,同时确认下方图内容已经勾选 在cmd下使用命令:wsl --install 安装wsl2。因 wsl2 默认安装ubuntu系统。如果安装wsl2后没有安装ubuntu系统,则需要去谷歌商店里搜索…

AutoJs案例---登录界面

"ui";showLoginUI(); ui.statusBarColor("#000000") //顶部颜色为黑色//显示登录界面 function showLoginUI(){ui.layout(<frame><vertical h"auto" align"center" margin"0 50"><linear><text w"…

Nextcloud实现协同办公 -V2

V1在这里&#xff1a;https://blog.csdn.net/philosophyatmath/article/details/130594037。V2没有继续使用onlyoffice而是使用nextcloud office Nextcloud 安装 LAMP&#xff08;LinuxApacheMySQLPHP&#xff09;方式。局域网单机模式。 安装环境&#xff1a; OS:ubuntu 2…

【Redis】Redis配置以及常用命令

文章目录 一、关系型数据库 与 非关系型数据库1. 关系型数据库的概念2. 非关系型数据库的概念3. 关系型数据库和非关系型数据库区别3.1 数据存储方式不同3.2 扩展方式不同3.3 对事务性的支持不同 4. 非关系型数据库产生背景5. 总结5.1 两组区别5.2 创建实例过程 二、Redis 的概…

【玩转Linux操作】详细讲解Shell的判断,循环语句

&#x1f38a;专栏【玩转Linux操作】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 大一同学小吉&#xff0c;欢迎并且感谢大家指出我的问题&#x1f970; 文章目录 &#x1f354;判断语句⭐单层if&#x1f388;示例 ⭐…

什么是动态住宅代理?

随着网络的迅速发展&#xff0c;许多人对代理IP已经有了比较深刻的认识&#xff0c;并且广泛地运用到了各自的业务中&#xff0c;尤其在跨境的相关业务中表现尤其卓越。对于代理IP的类别&#xff0c;也需要根据自己的业务类型具体选择最合适的&#xff0c;那么今天就给大家具体…

Redis之哨兵模式以及RedisTemplate的使用

Redis之哨兵模式 一 哨兵模式原理 说明&#xff1a; Sentinel具有三个作用&#xff1a;监控&#xff0c;故障转移和通知Sentinel如何判断Redis是否健康 ① 每隔1秒发送一次ping命令&#xff0c;如果超过一定时间没有响应则认为主观下线 ② 如果超过一半以上的sentinel认为实…

【软考网络管理员】2023年软考网管初级常见知识考点(25)- 网络地址转换NAT

涉及知识点 什么是NAT技术&#xff1f;静态网络地址转换&#xff0c;动态网络地址转换&#xff0c;基于端口的网络地址转换,NAPT的配置命令和动态NAT配置命令&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 原创于…