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

PostgreSQL维护——解决索引膨胀和数据死行

注意: 本文内容于 2024-09-16 00:40:33 创建,可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容,请访问原文地址:PostgreSQL维护——解决索引膨胀和数据死行。感谢您的关注与支持!

我有一张表,为了保障查询的快速响应,我是在必要的字段上建立了索引。该表的数量基数不变,只是每分钟会更新过来一批数据,如此运行了一年之久,目前即使走索引查询,依旧特别慢。

排查主要是由两个现象导致的。

  1. 索引膨胀
  2. 数据死行

这两个现象是如何出现的呢?频繁的进行insert/update/delete就会出现。

一、复现

下面来简单复现一下。

复现步骤

  1. 创建新表、关闭自动vacuum。vacuum如果开启,就会导致死行问题不复现。
  2. 录入样本数据、并创建索引,默认采用btree。我设置的样本数据为2000行。
  3. 执行insert/update/delete操作,执行10000次,并保持最后的数据总量仍然为2000条。
    • insert && delete:删除的数据作为新数据插入
    • update:更新其他数据字段
  4. 分别比较执行前与执行后的索引总量、死行数量。

以上复现步骤,我已经编写了一套脚本。开箱即用。

脚本地址

取其中一套运行结果,如下

大小/操作阶段insert && delete 前insert && delete 后update 前update 后
索引144KB832KB144KB976KB
数据136KB640KB136KB384KB
死行0行9754行0行9850行

2000条数据在频繁进行insert/delete/update之后,就会导致索引异常膨胀,是原数据大小的几倍之多。即使数据的总数未变,占用的数据空间仍然变大,因为数据死行导致的。

这两个问题,都会直接或者间接影响到数据库的查询速率。如果

二、临时解决方案

数据死行与索引膨胀,根源在于PostgreSQL的MVCC(Multi-Version Concurrency Control),有兴趣可以查阅官方文档。

2.1 数据死行

在 PostgreSQL 中,UPDATE 操作不会直接修改原有行,而是生成一条新的行记录,同时将原有行标记为“死行”,但并不会立即删除它。类似于 UPDATE,DELETE 操作不会立即删除数据行,而是将其标记为“死行”,并等待后续的 VACUUM 操作来真正回收空间。因此,频繁的 DELETE 操作也会导致死行积累。

临时解决方式:vaccum

VACUUM 是 PostgreSQL 中的垃圾收集机制,用于回收那些不再使用的“死”行空间,但不会释放表文件大小。它会标记那些不再使用的行,可以被新数据覆盖,操作过程中不会锁表。

-- vacuum整个库
vacuum;
-- vacuum单个表
vacuum 表名;

其中涉及到的查询死行的SQL如下

select n_dead_tup from pg_stat_user_tables where n_dead_tup>0 and relname='test_data';

2.2 索引膨胀

在 PostgreSQL 中,每次执行 UPDATE 或 DELETE 操作时,并不会直接修改或删除原有的记录。

相反,旧的版本(元组)会被标记为“死行”,而新的数据将作为一个新的行版本插入表中。

对于索引字段,原有的索引条目不会立即被删除,而是新建一个指向新数据的索引条目。因此,频繁的 UPDATE 和 DELETE 会导致索引包含大量的过时条目,引发索引膨胀、查询效率降低。

临时解决方式:重建索引

最简单的方式是先删再增

比较方便的方式是reindex,但是该方式会在重建的过程中锁表。

reindex table 表名;
reindex index 索引名;

自从PostgreSQL12开始,reindex支持并行重建索引,不会锁表,但比直接reindex要慢。

reindex table concurrently 表名;
reindex index concurrently 索引名;

其中涉及到的一些查询索引大小的SQL如下

-- 查询表内总数据大小
select pg_table_size('test_data'),pg_size_pretty(pg_table_size('test_data'));

-- 查询表内总索引大小
select pg_indexes_size('test_data'),pg_size_pretty(pg_indexes_size('test_data'));

-- 查询表的总大小,包含数据和索引
select pg_total_relation_size('test_data'),pg_size_pretty(pg_total_relation_size('test_data'));

三、设计层面规避该问题

单表数据量大,创建索引来加速查询效率,同时又要进行增删改数据。使用单表的做法在短时间内,是个轻量快速的好做法,适合敏捷式开发。但是长时间来看,还需要经常的人工维护,或者配置数据库的定时清理机制(又会存在锁表等问题),这并不好。

在大型项目上,以上做法就不太适合了。因此在设计上,可以做读写分离,实现一劳永逸。

  • 专门的写表,不创建索引,用来快速增删改。

  • 专门的读表,创建索引,用来快速查询。读表数据来源于写表,这之间需要设计一套适应当前需求的同步机制。

这套同步机制,根据我当前的需求,主要有两种同步方式。

  1. 批量增量同步:程序上定期将写表中增量的数据,一次性批量更新到读表。周期根据需求设定。
  2. 全量同步:读表增量同步也会存在膨胀的问题,因此可以定期的将备份一张新的全量读表,替换掉原来的读表。

在PostgreSQL中,如果数据量不是特别大,读表不需要单独建表去维护,直接使用物理视图即可,根据周期定时刷新。

物理视图跟逻辑视图的区别

逻辑视图需要在查询视图时,根据视图逻辑实时查询,与原数据表相比不存在数据差异性问题。

物理视图相当于创建了一张临时表并存储到了磁盘,不会自动更新,需要人工维护。

举例物理视图用法

-- 重建物理视图
drop materialized view if exists view_name;
create materialized view view_name AS
select * from test_data;
-- 锁表更新物理视图
refresh materialized view view_name;
-- 并行更新物理视图,首先需要物理视图有唯一键
create unique index unique_id_index ON view_name (id);
refresh materialized view concurrently view_name;

http://www.kler.cn/news/308897.html

相关文章:

  • 运维的基本概念:服务器和网络基础知识
  • 瑞星微RK芯片的Buildroot构建系统镜像
  • 【Gateway】Gateway Filter Factories
  • Visual Studio 2019/2022 IntelliCode(AI辅助IntelliSense)功能介绍
  • 【SpringBoot】调度和执行定时任务--Spring Task(超详细)
  • 数据结构 - 树与二叉树
  • [强化你的LangChain工具创建技能:从基础到进阶]
  • C语言 | Leetcode C语言题解之第413题等差数列划分
  • c语言题目猜凶手问题
  • Vue2中父子组件通信双向绑定
  • 【Java】【力扣】83.删除排序链表中的重复元素
  • TensorRT-LLM——优化大型语言模型推理以实现最大性能的综合指南
  • react18基础教程系列-- 框架基础理论知识mvc/jsx/createRoot
  • 预训练蛋白质语言模型ESM-2保姆级使用教程
  • C++设计模式(更新中)
  • 数据结构:(OJ141)环形列表
  • 李宏毅2023机器学习HW15-Few-shot Classification
  • 部分动态铜皮的孤岛无法删除。报错
  • Linux下的CAN通讯
  • 深度学习中实验、观察与思考的方法与技巧
  • JavaScript:驱动现代Web应用的关键引擎及其与HTML/CSS的集成
  • 数模原理精解【11】
  • el-table 如何实现行列转置?
  • C#读取应用配置的简单类
  • 软件测试工程师面试整理-常见面试问题
  • 后端Controller获取成功,但是前端报错404
  • etcd入门指南:分布式事务、分布式锁及核心API详解
  • 企业开发时,会使用sqlalchedmy来构建数据库 结构吗? 还是说直接写SQL 语句比较多?
  • 断电重启之后服务器都有哪些服务需要重启
  • 828华为云征文|docker部署kafka及ui搭建