POSTGRESQL ERROR

本文详细解析了PostgreSQL中TOAST存储机制的工作原理,包括如何处理超长字段、不同存储策略的选择及其对性能的影响。同时,介绍了如何诊断和解决TOAST相关的常见问题。
 

missing chunk number x for toast value x in pg_toast_x

相关知识

  • toast是The OverSized Attribute Storage Technique(超尺寸字段存储技术)的缩写,是超长字段在pg中的一种存储方式。
  • pg采用的存储默认是每个页面存储固定8Kb大小的数据,并且元组(行记录)不允许跨页面存储,所以并不能直接存储大字段数据。
  • 所以toast会将大字段值压缩或者分散为多个物理行来存储。
  • pg的部分类型数据支持toast,因为不是所有字段都会产生大字段数据的,完全没必要用到Toast技术(比如date,time,boolean等)。
  • 支持Toast的数据类型应当时变长的(variable-length)。
  • 当表中字段任何一个有Toast,那这个表都会有这一个相关联的Toast表。
  • OID被存储在pg_class.reltoastrelid里面。
  • 超出的数值将会被分割成chunks,并最多toast_max_chunk_size 个byte(缺省是2Kb),
  • 当存储的行数据超过toast_tuple_threshold值(通常是2kB),就会触发toast存储,这时toast将会压缩或者移动字段值直到超出部分比toast_tuple_targer值小(这个值通常也是2KB)。
  • 相比较普通表(MAIN TABLE),TOAST有额外的三个字段

    chunk_id:标识TOAST表的OID字段
    chunk_seq:chunk的序列号,与chunk_id的组合唯一索引可以加速访问
    chunk_data:存储TOAST表的实际数据

  • toast有4种存储策略:

    PLAIN:避免压缩和行外存储。只有那些不需要TOAST策略就能存放的数据类型允许选择(例如int类型),而对于text这类要求存储长度超过页大小的类型,是不允许采用此策略的

    EXTENDED:允许压缩和行外存储。一般会先压缩,如果还是太大,就会行外存储

    EXTERNA:允许行外存储,但不许压缩。类似字符串这种会对数据的一部分进行操作的字段,采用此策略可能获得更高的性能,因为不需要读取出整行数据再解压。

    MAIN:允许压缩,但不许行外存储。不过实际上,为了保证过大数据的存储,行外存储在其它方式(例如压缩)都无法满足需求的情况下,作为最后手段还是会被启动。因此理解为:尽量不使用行外存储更贴切。

  • 举例说明
    创建表

postgres=# create table blog(id int, title text, content text);
CREATE TABLE
postgres=# \d+ blog;
                          Table "public.blog"
 Column  |  Type   | Modifiers | Storage  | Stats target | Description 
---------+---------+-----------+----------+--------------+-------------
 id      | integer |           | plain    |              | 
 title   | text    |           | extended |              | 
 content | text    |           | extended |              |

可以看到,interger默认TOAST策略为plain,而text为extended。PG资料告诉我们,如果表中有字段需要TOAST,那么系统会自动创建一张TOAST表负责行外存储,那么这张表在哪里?

postgres=# select relname,relfilenode,reltoastrelid from pg_class where relname='blog';
 relname | relfilenode | reltoastrelid 
---------+-------------+---------------
 blog    |       16441 |         16444
(1 row)

通过上诉语句,我们查到blog表的oid为16441,其对应TOAST表的oid为16444(关于oid和pg_class的概念,请参考PG官方文档),那么其对应TOAST表名则为:pg_toast.pg_toast_16441(注意这里是blog表的oid),我们看下其定义:

postgres=# \d+ pg_toast.pg_toast_16441;
TOAST table "pg_toast.pg_toast_16441"
   Column   |  Type   | Storage 
------------+---------+---------
 chunk_id   | oid     | plain
 chunk_seq  | integer | plain
 chunk_data | bytea   | plain

TOAST表有3个字段:

  • chunk_id:用来表示特定TOAST值的OID,可以理解为具有同样chunk_id值的所有行组成原表(这里的blog)的TOAST字段的一行数据
  • chunk_seq:用来表示该行数据在整个数据中的位置
  • chunk_data:实际存储的数据。

现在我们来实际验证下:

postgres=# insert into blog values(1, 'title', '0123456789');
INSERT 0 1
postgres=# select * from blog;
 id | title |  content   
----+-------+------------
  1 | title | 0123456789
(1 row)
postgres=# select * from pg_toast.pg_toast_16441;
 chunk_id | chunk_seq | chunk_data 
----------+-----------+------------
(0 rows)

可以看到因为content只有10个字符,所以没有压缩,也没有行外存储。然后我们使用如下SQL语句增加content的长度,每次增长1倍,同时观察content的长度,看看会发生什么情况?

postgres=# update blog set content=content||content where id=1;
UPDATE 1
postgres=# select id,title,length(content) from blog;
 id | title | length 
----+-------+--------
  1 | title |     20
(1 row)
postgres=# select * from pg_toast.pg_toast_16441;
 chunk_id | chunk_seq | chunk_data 
----------+-----------+------------
(0 rows)

反复执行如上过程,直到pg_toast_16441表中有数据:

postgres=# select id,title,length(content) from blog;
 id | title | length 
----+-------+--------
  1 | title | 327680
(1 row)
postgres=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16441;
 chunk_id | chunk_seq | length 
----------+-----------+--------
    16439 |         0 |   1996
    16439 |         1 |   1773
(2 rows)

可以看到,直到content的长度为327680时(已远远超过页大小8K),对应TOAST表中才有了2行数据,且长度都是略小于2K,这是因为extended策略下,先启用了压缩,然后才使用行外存储

下面我们将content的TOAST策略改为EXTERNA,以禁止压缩。

postgres=# alter table blog alter content set storage external;
ALTER TABLE
postgres=# \d+ blog;
                          Table "public.blog"
 Column  |  Type   | Modifiers | Storage  | Stats target | Description 
---------+---------+-----------+----------+--------------+-------------
 id      | integer |           | plain    |              | 
 title   | text    |           | extended |              | 
 content | text    |           | external |              |

然后我们再插入一条数据:

postgres=# insert into blog values(2, 'title', '0123456789');
INSERT 0 1
postgres=# select id,title,length(content) from blog;
 id | title | length 
----+-------+--------
  1 | title | 327680
  2 | title |     10
(2 rows)

然后重复以上步骤,直到TOAST表中产生新的行:

postgres=# update blog set content=content||content where id=2;
UPDATE 1
postgres=# select id,title,length(content) from blog;
 id | title | length 
----+-------+--------
  2 | title |   2560
  1 | title | 327680
(2 rows)
postgres=# select chunk_id,chunk_seq,length(chunk_data) from pg_toast.pg_toast_16441;
 chunk_id | chunk_seq | length 
----------+-----------+--------
    16447 |         0 |   1996
    16447 |         1 |   1773
    16448 |         0 |   1996
    16448 |         1 |    564
(4 rows)

这次我们看到当content长度达到2560(按照官方文档,应该是超过2KB左右),TOAST表中产生了新的2条chunk_id为16448的行,且2行数据的chunk_data的长度之和正好等于2560。

  • toast的优缺点
    1.可以存储超长超大字段,避免之前不能直接存储的限制
    2.物理上与普通表是分离的,检索查询时不检索到该字段会极大地加快速度
    3.更新普通表时,该表的Toast数据没有被更新时,不用去更新Toast表

  • toast的劣势:
    1.对大字段的索引创建是一个问题,有可能会失败,其实通常也不建议在大字段上创建,全文检索倒是一个解决方案。
    2.大字段的更新会有点慢,其它DB也存在,通病

报错原因

某张表关联的toast表的data发生损坏。

解决方法

1、 定位是哪张表的toast有问题:

select 2619::regclass; 
pg_statistic

2、 找到哪个表有问题后,先对该表做一下简单的修复:

REINDEX table pg_toast.pg_toast_2619;
REINDEX table pg_statistic;
VACUUM ANALYZE pg_statistic;

3、 定位该表中损坏的数据行。执行

DO $$
declare
	 v_rec record;
BEGIN	
	for v_rec in SELECT * FROM pg_statistic loop
	        raise notice 'Parameter is: %', v_rec.ctid;
		raise notice 'Parameter is: %', v_rec;
	end loop; 
END;
$$
  LANGUAGE plpgsql;

4、 将第3步中定位的记录删除:

delete from pg_statistic where ctid ='(50,3)';

5、 重复执行第3,4步,直到全部有问题的记录被清除。
6、 至此,toast问题就解决完了,解决之后,对数据库进行一次完整的维护或者索引重建。

invalid page header in block x of relation base/x/x

相关知识

  • 参数zero_damaged_pages是bool型的。
  • 默认是off,意思是系统遇到这类因磁盘、内存等硬件引起的问题就会给出这样一份错误提示。
  • 当设置为on时,就可以忽略这些错误报告,并擦除掉这些损坏的数据,没受影响的数据还是正常的。

报错原因

系统检测到磁盘页损坏,并导致postgresql数据取消当前的事务并提交一份错误报告信息。

解决方法

1、  关闭数据库服务器。
2、  编辑postgresql.conf文件,最后一行加入:zero_damaged_pages = on。保存文件,退出。
3、  启动数据库服务器,确认数据库服务是否恢复运行。
4、  关闭数据库服务器。
5、  编辑postgresql.conf文件,去掉最后一行zero_damaged_pages = on。保存文件,退出。
6、  重启数据库。

missing chunk number x for toast value x in pg_toast_x

相关知识

Pg的事务是依靠多版本控制实现的,即一条记录更改前及更改后其实是两条数据,并且都存储在当前表里。这两条记录除了本身的逻辑信息之外,还存有相关事务信息。这些事务信息随着事务的改变而改变。当数据库当前事务id和某一条记录的事务id匹配时,那么这条匹配的记录就会显示。所以,当事务非正常结束时,可能会使某一条记录的事务信息没有更新正确,那么此时我们看到的就可能是两条相同主键的数据。实际上,这是一条数据的两种不同状态。这时就出现了上面的错误。

  • postgresql 多版本并发控制(MCC)

    Postgres用多版本并发控制方式管理事务(Multiversion concurrency control),这点上与Oracle非常相似,后者也采用多版本并发控制机制。

MCC的特点:(通俗点儿)
读不产生独占锁,可以共享,不影响其他事务的读写;

写产生独占锁,数据可以共享,不影响读,但不能同时写;

MCC的目的就是最大化实现并发对数据的访问,postgresql与oracle实现的效果相同,只是方式不同。实质是对事务修改数据的管理方式采用不同方式。

postgresql将修改前后的数据都存储在相同的结构中(新旧版本都存在表中)。两个事务同时访问记录时,通过参考tmin和tmax的标记判断记录的版本,根据版本号与自己当前的事务标识比较,确定自己的数据权限。当事务发生修改或者删除记录后,空间没有立即释放,实质并没有在原来的存储位置上进行更新,而是使用了新的存储空间。系统通过周期性的运行vaccum进程来回收之前的存储空间(与Java虚拟机的垃圾回收机制有点象)。事务提交前,只需要访问原来的数据即可;提交后,系统更新元组的存储标识,直到vaccum进程收回为止。

oracle也是根据事务标识的比较确认数据访问权限。当事务发生修改或者删除记录,oracle将原数据存储在undo段中,事务提交后直接更新相应记录的存储位置,并没有直接使用新的存储空间。oracle的undo段同样用于数据库的介质恢复,在应用完redo日志后(前滚),通过应用undo段中的事务,对数据库进行回滚。

oracle在10g中引入的flashback功能中的flashback drop功能与postgresql的方式就有点类似,只是针对表进行删除标记,并没有释放表所占用的空间,通过对表进行改名。在数据库管理员进行recyclebin操作后,才能释放表所占空间。

postgresql尽管是分配新的空间,对空间的分配很快。在postgresql中也有相应的参数对其进行限制,当更新的数据达到一定的程度时将会直接出发vacuum进行,直接释放掉原来的空间。postgresql与oracle对delete操作产生的空间使用,基本相同。对update操作产生的空间使用不同,相对oracle来说,postgresql可能会产生更多的行迁移或迁移的,这样对性能影响应该是存在的。

报错原因

主键重复,导致重建索引报错。

解决方法

1、  根据报错信息定位问题记录。
2、  将问题记录拷贝出,并判断正确状态的数据行。
3、  将正确的记录拷贝会表里。
4、  对数据库重建索引。
Docker是一种容器化平台,允许用户轻松创建、部署和运行应用程序。当在Docker中运行PostgreSQL时,可能会出现错误响应。以下是一些常见的错误响应及可能的解决方案: 1. 容器无法启动:当尝试运行PostgreSQL容器时,容器管理程序可能会报错并拒绝启动。这可能是由于端口冲突导致的。在运行容器时,确保指定的端口与主机上的其他服务不冲突。 2. 数据库连接问题:当尝试连接到PostgreSQL数据库时,可能会收到错误响应。这可能是由于连接参数不正确或数据库服务未正确配置导致的。请确保使用正确的主机、端口、用户名和密码来连接到数据库,并确保数据库服务已启动和运行。 3. 数据库文件权限问题:在某些情况下,PostgreSQL容器可能会报告关于数据库文件权限的错误响应。这可能是由于容器内部的文件系统权限配置不正确导致的。在启动容器时,可以尝试使用`--user`选项来指定运行容器的用户,并确保该用户具有正确的权限。 4. 容器资源限制:当PostgreSQL容器无法分配所需的资源时,可能会出现错误响应。这可能是由于Docker宿主机上的资源限制导致的。可以尝试增加Docker宿主机的内存、CPU等资源限制,以便容器能够正确运行。 总之,解决Docker中运行PostgreSQL出现的错误响应需要仔细检查容器配置、数据库连接参数、文件权限和宿主机资源限制等方面。如果问题仍然存在,可以查看Docker和PostgreSQL的官方文档或寻求专业的技术支持。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值