当前位置: 首页 > news >正文

pg 唯一性约束修复

先说一下背景,之前我用的 pg 镜像是 postgres:11.18-bullseye,后来被升级成了 postgres:11.20-alpine3.18,这个造成的其中一个后果简而言之是 pg 对字符串的排序发生了变化,比如原先认为 A > B,现在则变成了 A < B。由此,就有可能破坏数据的唯一性约束。不仅如此,只要索引列包含字符串类型的字段,也会有问题。

为了方便继续描述,假设有个 test 表,结构如下

字段名类型说明
namevarchar(128)主键
ageint

为了加深理解,假设这个表已经包含了重复数据,重复数据的 name = ‘A’,age 分别是 11 和 21。

思考一下如下这两条 SQL 的结果分别是什么

select name, count(*) from test group by name;select name, count(*) from test where name = 'A' group by name;

第一条 SQL 语句大概率会返回 ('A', 2),第二条大概率会返回 ('A', 1)。造成这个现象的原因是第一条语句是全表扫描,第二条语句是走的索引。

如何解决

这种情况除了重建整个数据库的索引外,没有其他办法。但是在重建唯一索引之前,需要先删除重复的数据以维护唯一性约束。

这个问题貌似很简单,但要处理如下几个事情:

  1. 找出重复的数据
  2. 区分重复的数据
  3. 确定数据保留标准,这里假设保留新数据。

找出重复的数据已经有办法了,通过 group by 和 count(*) 可以找出哪些数据重复了。但是剩下两个事情呢,考虑下我们上文提的例子,如何为 test 表确定哪个数据更新呢?test 表可没有自增主键ID,也没有相关的时间字段。建表不规范让这个问题更难处理。

pg 的系统字段

让我们先了解下 pg 中的隐藏字段

  1. ctid。他记录的是行的物理存储信息,即使两条数据完全一样,ctid 也会不一样。但是 ctid 是易变的,比如更新之后,ctid 也会变化,所以需要考虑并发问题。
  2. xmin。他主要用在 MVCC 中,记录的是行被插入时的事务 ID,pg 通过比较行的事务 ID 和 当前事务 ID 的大小来判断该行对当前事务是否可见。所以事务 ID 一般情况下是递增的。但是也不能完全通过 xmin 的大小判断哪个行是先插入的,原因如下:
    1. MVCC 机制使得在更新记录的时候,其实是写入行的新版本,新版本行的 xmin 是会变的。
    2. 事务 ID 是32位的,在数据库长久运行后,事务ID会产生回卷,即从 3 开始继续递增,详见   防止事务ID回卷失败

解决方案

思虑再三,我没有发现一个普适的方案,能够解决如下这个问题,如果有的话,希望有个好心人告知我一下

在某个表已经破坏了唯一性约束后,若这个表没有 自增ID,创建时间,更新时间等可以标志记录写入先后顺序字段的话,很难去删除旧的重复数据

不过还好,我的工作并不是需要去证明什么,有时候也不需要完美的解决方案,在借助上面提到的 ctid 和 xmin 字段,再加上业务背景,我的解决方案是:

  1. 禁用索引
  2. 通过 group by 找出重复的记录
  3. 获取重复记录的 ctid 和 xmin,有些表还有 created_at 字段,一并获取
  4. 通过 order by created_at desc, xmin desc,获取到第一条记录,并认为它是最新的,保留它,通过 ctid 删除其他所有

话外

这个事情是由于升级 pg 版本导致的,对于升级这个事情,我的观点还是保守的,能不升就不升。不过这里还暴露出来了另外一个事情,建表的规范。如果我们的表都满足如下两个要求,这个事情就会简单很多:

  1. 使用自增主键。
  2. 带上 created_at, updated_at 字段

有时候挺奇怪的,可能每个研发,每个团队的关注点不一样,但是像这种建表的规范不应该是最基础的吗?我们的所有工作都是围绕数据存储展开,那最基本的表的结构,索引的创建,是不是也应该受到更多关注,成为各种评审的重中之重。前人挖坑,后人填坑,后人还有后来人。

在思考方案的过程中,我还想到过另外一个方案,简述如下:

  1. 通过 group by 找到重复的数据
  2. 通过走对应的唯一索引,查出当前能查到的那条记录。这条记录认为是最新的,删除其他重复记录

但是这个有如下几个问题:

  1. 如何确保一定走了唯一索引,有两种方式:
    1. pg 可以通过装个扩展,从而实现类似 MySQL 的索引提示功能
    2. 每次只查一条记录,这个基本上应该是走的索引,除非你的表数据量足够的小。除此之外,若重复记录较多,则效率也慢
  2. 即使走了唯一索引,一定会只返回一条记录吗(这个没实验过,等待一个好心人)。之所以有这个顾虑,是考虑到B+树的叶子结点,一般对应的是数据页,数据库页里包含多个数据记录。如果一次查询,返回的数据页里恰好同时包含了重复的记录呢,这个时候不知道是否会返回两条重复的记录。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Go语言的编程规则和秘籍
  • dll高级技术--动态注入:ImgWalk动态库,这个DLL用来检测被注入的进程中当前载入的各个模块名称---DLL程序
  • 公安智慧大楼信息化整体建设设计方案
  • Web开发 Ajax 2024/3/31
  • [pytorch] --- pytorch基础之transforms
  • TLS握手性能测试工具:快速重置、多线程与高级统计分析(C/C++代码实现)
  • 深入理解指针(5)
  • 【零知识证明】Groth16
  • 磐石云语音识别引擎
  • 笔记本电脑中怎么查看光驱
  • [VirtualBox+ubuntu24]设置linux学习环境
  • C++笔记14•二叉树之二叉搜索树•
  • while (r > b[i].r) del(a[r--]); while (r < b[i].r)
  • C/C++逆向:寻找mian函数(其他编译配置特征)
  • 信息安全数学基础(1)整除的概念
  • @jsonView过滤属性
  • dva中组件的懒加载
  • emacs初体验
  • Git 使用集
  • Kibana配置logstash,报表一体化
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • MySQL用户中的%到底包不包括localhost?
  • react-native 安卓真机环境搭建
  • SpiderData 2019年2月25日 DApp数据排行榜
  • SQLServer之创建显式事务
  • Vue ES6 Jade Scss Webpack Gulp
  • vue学习系列(二)vue-cli
  • Web设计流程优化:网页效果图设计新思路
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 第十八天-企业应用架构模式-基本模式
  • 高性能JavaScript阅读简记(三)
  • 码农张的Bug人生 - 初来乍到
  • 前端技术周刊 2019-02-11 Serverless
  • 人脸识别最新开发经验demo
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 一文看透浏览器架构
  • 异常机制详解
  • 选择阿里云数据库HBase版十大理由
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ‌‌雅诗兰黛、‌‌兰蔻等美妆大品牌的营销策略是什么?
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • # SpringBoot 如何让指定的Bean先加载
  • #ubuntu# #git# repository git config --global --add safe.directory
  • #微信小程序(布局、渲染层基础知识)
  • #我与Java虚拟机的故事#连载02:“小蓝”陪伴的日日夜夜
  • (145)光线追踪距离场柔和阴影
  • (LeetCode 49)Anagrams
  • (php伪随机数生成)[GWCTF 2019]枯燥的抽奖
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (四)stm32之通信协议
  • (五)网络优化与超参数选择--九五小庞
  • (一)认识微服务
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)