PG常见错误集合

来源:互联网 发布:网络诈骗案一般关多久 编辑:程序博客网 时间:2024/05/25 21:34

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 TABLEpostgres=# \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 1postgres=# 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 1postgres=# 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 1postgres=# 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 1postgres=# 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;BEGINfor 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、  对数据库重建索引。
原创粉丝点击