zoukankan      html  css  js  c++  java
  • 腾讯云数据库团队:PostgreSQL TOAST技术理解

    作者介绍:胡彬 腾讯云高级工程师

    TOAST是“The Oversized-Attribute Storage Technique”的缩写,主要用于存储一个大字段的值。要理解TOAST,我们要先理解页(BLOCK)的概念。在PG中,页是数据在文件存储中的基本单位,其大小是固定的且只能在编译期指定,之后无法修改,默认的大小为8KB。同时,PG不允许一行数据跨页存储,那么对于超长的行数据,PG就会启动TOAST,具体就是采用压缩和切片的方式。如果启用了切片,实际数据存储在另一张系统表的多个行中,这张表就叫TOAST表,这种存储方式叫行外存储。

    在深入细节之前,我们要先了解,在PG中每个表字段有四种TOAST的策略:

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

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

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

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

    首先创建一张blog表:

    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优先选择压缩
    • 不管是否压缩,一旦数据超过2KB左右,就会启用行外存储
    • 修改TOAST策略,不会影响现有数据的存储方式

     相关阅读:

    存储总量达20T的MySQL实例,如何完成迁移?

     腾讯存储与云存储Redis免费体验


     此文已由作者授权腾讯云技术社区发布,转载请注明文章出处,原文链接https://www.qcloud.com/community/article/164816001481011925

    获取更多云计算技术干货,可请前往腾讯云技术社区,欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~

  • 相关阅读:
    IE6中overflow:hidden失效怎么办
    单例模式笔记
    linux 中的 "2>&1"含义
    linux 文件目录介绍
    centos 安装jdk
    SimpleDateFormat非线程安全
    Linux下Weblogic 11g R1安装和配置
    <meta>标签 的一些用法
    基于java的邮件群发软件
    史上最完整的集合类总结及hashMap遍历
  • 原文地址:https://www.cnblogs.com/qcloud1001/p/6641088.html
Copyright © 2011-2022 走看看