我有一个PostgreSQL表。 select *非常慢,而select id又好又快。我认为这可能是因为该行的尺寸很大,需要一段时间才能运输,或者可能是其他因素。
我需要所有字段(或几乎所有字段),因此选择只是一个子集不是快速解决方案。选择我想要的字段仍然很慢。
这是我的表架构减去名称:
integer                  | not null default nextval('core_page_id_seq'::regclass)
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
integer                  | not null default 0
text                     | default '{}'::text
text                     | 
timestamp with time zone | 
integer                  | 
timestamp with time zone | 
integer                  | 

文本字段的大小可以是任何大小。但是,即使在最坏的情况下,也不会超过几千字节。
问题

有没有什么尖叫着“疯狂的低效率”的东西?
有没有办法测量页面大小在Postgres命令行上可以帮助我调试吗?


评论

实际上...列之一是11 MB。我认为这可以解释这一点。那么有没有办法做length(*)而不仅仅是length(field)?我知道不是字符,而是字节,但是我只需要一个近似值。

#1 楼

问题2:way to measure page size

PostgreSQL提供了许多数据库对象大小函数。我在此查询中打包了最有趣的内容,并在底部添加了一些统计信息访问功能。 (附加的pgstattuple模块提供了更多有用的功能。)
这将显示出不同的测量“行大小”的方法会导致非常不同的结果。这完全取决于您要测量的内容。
此查询需要Postgres 9.3或更高版本。对于较旧的版本,请参见下文。
VALUES子查询中使用LATERAL表达式,以避免拼写出每一行的计算。
public.tbl替换为您的可选模式限定的表名,以获得所收集行的紧凑视图尺寸统计。您可以将其包装到plpgsql函数中以供重复使用,将表名称作为参数输入并使用EXECUTE ...
SELECT l.metric, l.nr AS "bytes/ct"
     , CASE WHEN is_size THEN pg_size_pretty(nr) END AS bytes_pretty
     , CASE WHEN is_size THEN nr / NULLIF(x.ct, 0) END AS bytes_per_row
FROM  (
   SELECT min(tableoid)        AS tbl      -- = 'public.tbl'::regclass::oid
        , count(*)             AS ct
        , sum(length(t::text)) AS txt_len  -- length in characters
   FROM   public.tbl t                     -- provide table name *once*
   ) x
 , LATERAL (
   VALUES
      (true , 'core_relation_size'               , pg_relation_size(tbl))
    , (true , 'visibility_map'                   , pg_relation_size(tbl, 'vm'))
    , (true , 'free_space_map'                   , pg_relation_size(tbl, 'fsm'))
    , (true , 'table_size_incl_toast'            , pg_table_size(tbl))
    , (true , 'indexes_size'                     , pg_indexes_size(tbl))
    , (true , 'total_size_incl_toast_and_indexes', pg_total_relation_size(tbl))
    , (true , 'live_rows_in_text_representation' , txt_len)
    , (false, '------------------------------'   , NULL)
    , (false, 'row_count'                        , ct)
    , (false, 'live_tuples'                      , pg_stat_get_live_tuples(tbl))
    , (false, 'dead_tuples'                      , pg_stat_get_dead_tuples(tbl))
   ) l(is_size, metric, nr);

结果:
              metric               | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
 core_relation_size                | 44138496 | 42 MB        |            91
 visibility_map                    |        0 | 0 bytes      |             0
 free_space_map                    |    32768 | 32 kB        |             0
 table_size_incl_toast             | 44179456 | 42 MB        |            91
 indexes_size                      | 33128448 | 32 MB        |            68
 total_size_incl_toast_and_indexes | 77307904 | 74 MB        |           159
 live_rows_in_text_representation  | 29987360 | 29 MB        |            62
 ------------------------------    |          |              |
 row_count                         |   483424 |              |
 live_tuples                       |   483424 |              |
 dead_tuples                       |     2677 |              |

对于旧版本(Postgres 9.2或更高版本):
WITH x AS (
   SELECT count(*)               AS ct
        , sum(length(t::text))   AS txt_len  -- length in characters
        , 'public.tbl'::regclass AS tbl      -- provide table name as string
   FROM   public.tbl t                       -- provide table name as name
   ), y AS (
   SELECT ARRAY [pg_relation_size(tbl)
               , pg_relation_size(tbl, 'vm')
               , pg_relation_size(tbl, 'fsm')
               , pg_table_size(tbl)
               , pg_indexes_size(tbl)
               , pg_total_relation_size(tbl)
               , txt_len
             ] AS val
        , ARRAY ['core_relation_size'
               , 'visibility_map'
               , 'free_space_map'
               , 'table_size_incl_toast'
               , 'indexes_size'
               , 'total_size_incl_toast_and_indexes'
               , 'live_rows_in_text_representation'
             ] AS name
   FROM   x
   )
SELECT unnest(name)                AS metric
     , unnest(val)                 AS "bytes/ct"
     , pg_size_pretty(unnest(val)) AS bytes_pretty
     , unnest(val) / NULLIF(ct, 0) AS bytes_per_row
FROM   x, y

UNION ALL SELECT '------------------------------', NULL, NULL, NULL
UNION ALL SELECT 'row_count', ct, NULL, NULL FROM x
UNION ALL SELECT 'live_tuples', pg_stat_get_live_tuples(tbl), NULL, NULL FROM x
UNION ALL SELECT 'dead_tuples', pg_stat_get_dead_tuples(tbl), NULL, NULL FROM x;

结果相同。
Q1:anything inefficient?

您可以优化列顺序以节省每行一些字节,目前浪费了对齐填充:
integer                  | not null default nextval('core_page_id_seq'::regclass)
integer                  | not null default 0
character varying(255)   | not null
character varying(64)    | not null
text                     | default '{}'::text
character varying(255)   | 
text                     | default '{}'::text
text                     |
timestamp with time zone |
timestamp with time zone |
integer                  |
integer                  |

每行节省8到18个字节。我称其为“俄罗斯方块”。详细信息:

配置PostgreSQL的读取性能
计算和节省PostgreSQL中的空间

还请考虑:

索引查找的速度会明显更快当所有值均为36个字符时使用char vs varchar


评论


如果表为空,则9.3之前的代码片段将被零除。我实际上想使用9.3+版本,但是错误地选择了一个错误的版本,不得不花几个小时来修复它……现在我不能浪费所有的时间了。用,(LEAST(unnest(val),unnest(val)* ct))/(ct-1 + sign(ct))替换,unnest(val)/ ct不会抛出异常。理由是,当ct为0时,val将被替换为0,而ct将被替换为1。

–GuiRitter
19-10-17在13:58

@GuiRitter:感谢您指出。不过,我应用了一个更简单的修复程序。同时进行一些常规更新-但查询保持不变。

–欧文·布兰德斯特
19-10-17在23:30

#2 楼

通过查询整行的TEXT表示的长度,可以很容易地获得包括TOAST内容在内的行大小的近似值:

SELECT octet_length(t.*::text) FROM tablename AS t WHERE primary_key=:value;


这与执行时将在客户端获取的字节数非常接近:

SELECT * FROM tablename WHERE primary_key=:value;


...假设查询的调用者正在请求结果以文本格式显示,这是大多数程序可以执行的操作(可以使用二进制格式,但是在大多数情况下不值得这样做)。

可以使用相同的技术来定位N“文字”的tablename行:

SELECT primary_key, octet_length(t.*::text) FROM tablename AS t
   ORDER BY 2 DESC LIMIT :N;


评论


在处理大数据时快速获得一些估计的绝佳方法(例如,大多数行大小位于可变长度的toast存储列中),这是个好主意!

– fgblomqvist
19 Mar 27 '19在16:06

结果是字节?

–艾克拉·萨利霍夫(Akmal Salikhov)
19-10-21在15:18

@AkmalSalikhov是的,只需在文档中进行检查即可。我还有一个问题-我是否正确-octet_length给出了未压缩的大小,而不是磁盘上的实际大小?我发现这个答案对我来说很有意义。

– dankal444
7月2日,9:56



#3 楼

可能会发生一些事情。总的来说,我怀疑长度是近端问题。我怀疑您有一个与长度有关的问题。

您说文本字段最多可以达到k。主存储中的行不能超过8k,并且可能是较大的文本字段已被烤制,或从主存储中移出了单独文件中的扩展存储。这将使您的主存储速度更快(因此,选择id实际上会更快,因为要访问的磁盘页面更少),但选择*会变慢,因为随机I / O较多。

如果总行大小仍然全部在8k以下,您可以尝试更改存储设置。但是,我警告您,将超大属性插入主存储时可能会发生不好的事情,因此最好不要触摸此属性,如果需要,请通过检查约束设置适当的限制。因此,运输并不是唯一的选择。它可能正在整理许多需要随机读取的字段。大量的随机读取也可能会导致高速缓存未命中,并且所需的大量内存可能要求将事物具体化在磁盘上,并且需要大量的宽行(如果存在联接(如果涉及TOAST,则存在一个))可能需要更高的成本连接模式等。

我要做的第一件事是选择较少的行,看看是否有帮助。如果可行,您也可以尝试向服务器添加更多RAM,但是我将开始查看由于计划更改和缓存未命中而导致性能开始下降的地方。

#4 楼

使用上面提到的数据库对象大小函数:
q4312078q

评论


看起来很有希望,但是由于任何原因,在我看来,它都行不通。 pg_column_size(tablename.big_column)超出了pg_column_size(tablename。*)的值

–临qu
16 Mar 17 '16 at 17:03

#5 楼

如果需要平均当前行大小,则可以使用pg_column_size
SELECT SUM(pg_column_size(table_name.*))/COUNT(*) FROM tablename;

按列使用它:
SELECT SUM(pg_column_size(table_name.column_name))/COUNT(*) FROM tablename;