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

ops/2024/9/22 15:48:07/

注意: 本文内容于 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.ppmy.cn/ops/114306.html

相关文章

特殊类的设计与类型转换

特殊类的设计 1.请设计一个不能被拷贝的类 拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C98 C98是怎么设计的呢? class Cop…

音视频生态下Unity3D和虚幻引擎(Unreal Engine)的区别

技术背景 好多开发者跟我们做技术交流的时候,会问我们,为什么有Unity3D的RTMP|RTSP播放模块,还有RTMP推送和轻量级RTSP服务模块,为什么不去支持虚幻引擎?二者区别在哪里?本文就Unity3D和虚幻引擎之间的差异…

万能头无法使用

打开 新建 工具 编辑器选项 代码 这个缺省源 复制上这个 #ifndef _GLIBCXX_NO_ASSERT #include <cassert> #endif #include <cctype> #include <cerrno> #include <cfloat> #include <ciso646> #include <climits> #include <clocale&…

glTF格式:WebGL应用的3D资产优化解决方案

摘要 glTF作为一种高效的3D资产格式&#xff0c;为WebGL、OpenGL ES和OpenGL运行时的应用提供了强有力的支持。它不仅简化了3D模型的传输与加载流程&#xff0c;还通过优化资产大小&#xff0c;使得打包、解包更加便捷。本文将深入探讨glTF格式的优势&#xff0c;并提供实用的代…

指针修仙之实现qsort

文章目录 回调函数什么是回调函数回调函数的作用 库函数qsort使用qsort函数排序整形使用qsort函数排序结构体 qsort函数模拟实现说明源码and说明 回调函数 什么是回调函数 回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数…

react hooks--概述

前言 ◼ Hook 是 React 16.8 的新增特性&#xff0c;它可以让我们在不编写class的情况下使用state以及其他的React特性&#xff08;比如生命周期&#xff09;。 ◼ 我们先来思考一下class组件相对于函数式组件有什么优势&#xff1f;比较常见的是下面的优势&#xff1a; ◼ …

2024/9/21黑马头条跟学笔记(十)

1&#xff09;今日内容 1.1&#xff09;定时计算流程 1.不想用户看到的全是最新的&#xff0c;实时计算最火的推送 2.定时计算热度最高&#xff0c;存redis&#xff0c;推送到推荐页面 1.2&#xff09;使用schedule 多个服务部署&#xff0c;多次执行 硬编码定时时间在cron…

python基础(1)pyenv安装和对Django使用

pyenv安装 pyenv主要针对类 Unix 系统&#xff08;如 Linux、macOS&#xff09;用户&#xff0c;pyenv-win 是专为 Windows 开发的 pyenv 版本&#xff0c;允许您在不使用 WSL 的情况下管理多个 Python 版本和虚拟环境。 建议Git Bash&#xff1a; Powershell或Git Bash&…