web-dev-qa-db-fra.com

Mesurer la taille d'une ligne de table PostgreSQL

J'ai une table PostgreSQL. select * est très lent alors que select id est agréable et rapide. Je pense qu'il se peut que la taille de la rangée soit très grande et que cela prenne un certain temps à transporter, ou cela peut être un autre facteur.

J'ai besoin de tous les champs (ou presque tous), donc la sélection d'un seul sous-ensemble n'est pas une solution miracle. La sélection des champs que je veux est encore lente.

Voici mon schéma de table moins les noms:

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                  | 

La taille du champ de texte peut être de n'importe quelle taille. Mais encore, pas plus de quelques kilo-octets dans le pire des cas.

Des questions

  1. Y a-t-il quelque chose à ce sujet qui crie "fou inefficace"?
  2. Existe-t-il un moyen de mesurer la taille de la page sur la ligne de commande Postgres pour m'aider à déboguer cela?
94
Joe

Q2: way to measure page size

PostgreSQL fournit un certain nombre de fonctions de taille d'objet de base de données . J'ai emballé les plus intéressants dans cette requête et ajouté quelques Statistics Access Functions en bas. (Le module supplémentaire pgstattuple fournit encore des fonctions plus utiles.)

Cela va montrer que différentes méthodes pour mesurer la "taille d'une ligne" conduisent à des résultats très différents. Tout dépend exactement de ce que vous voulez mesurer.

Cette requête nécessite Postgres 9.3 ou version ultérieure . Pour les versions plus anciennes, voir ci-dessous.

Utilisation d'une expression VALUES dans une sous-requête LATERAL , pour éviter de préciser les calculs pour chaque ligne.

Remplacer public.tbl avec votre nom de table qualifié de schéma pour obtenir une vue compacte des statistiques de taille de ligne collectées. Vous pouvez envelopper cela dans une fonction plpgsql pour une utilisation répétée, remettre le nom de la table comme paramètre et utiliser 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);

Résultat:

              métrique | octets/ct | bytes_pretty | bytes_per_row 
 ----------------------------------- + -------- - + -------------- + --------------- 
 core_relation_size | 44138496 | 42 Mo | 91 
 Visibilité_map | 0 | 0 octets | 0 
 Free_space_map | 32768 | 32 kB | 0 
 Table_size_incl_toast | 44179456 | 42 Mo | 91 
 Indexes_size | 33128448 | 32 Mo | 68 
 Total_size_incl_toast_and_indexes | 77307904 | 74 Mo | 159 
 Live_rows_in_text_representation | 29987360 | 29 Mo | 62 
 --------------------------- | 
 row_count | 483424 | | 
 live_tuples | 483424 | | 
 dead_tuples | 2677 | |

Pour les versions plus anciennes ( Postgres 9.2 ou plus ):

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;

Même résultat.

Q1: anything inefficient?

Vous pouvez optimiser l'ordre des colonnes pour économiser quelques octets par ligne, actuellement gaspillés en remplissage d'alignement:

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                  |

Cela économise entre 8 et 18 octets par ligne. Je l'appelle "colonne tetris". Détails:

Considérez également:

105
Erwin Brandstetter

Une approximation de la taille d'une ligne, y compris le contenu de TOAST , est facile à obtenir en interrogeant la longueur de la représentation TEXT de la ligne entière:

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

Il s'agit d'une approximation proche du nombre d'octets qui seront récupérés côté client lors de l'exécution:

SELECT * FROM tablename WHERE primary_key=:value;

... en supposant que l'appelant de la requête demande des résultats au format texte, ce que font la plupart des programmes (le format binaire est possible, mais cela ne vaut pas la peine dans la plupart des cas).

La même technique pourrait être appliquée pour localiser les N "plus grandes lignes de texte" de tablename:

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

Il y a quelques choses qui pourraient arriver. En général, je doute que la longueur soit le problème proximal. Je soupçonne plutôt que vous avez un problème lié à la longueur.

Vous dites que les champs de texte peuvent atteindre quelques k. Une ligne ne peut pas dépasser 8 Ko dans le stockage principal, et il est probable que vos champs de texte plus grands ont été TOASTed , ou déplacés du stockage principal vers un stockage étendu dans des fichiers séparés. Cela rend votre stockage principal plus rapide (donc select id est en fait plus rapide car moins de pages de disque à accéder) mais select * devient plus lent car il y a plus d'E/S aléatoires.

Si la taille totale de vos lignes est toujours bien inférieure à 8 Ko, vous pouvez essayer de modifier les paramètres de stockage. Je voudrais cependant vous avertir que vous pouvez obtenir de mauvaises choses lors de l'insertion d'un attribut surdimensionné dans le stockage principal, il vaut donc mieux ne pas y toucher si vous n'y êtes pas obligé et si vous le faites, définissez des limites appropriées via des contraintes de vérification. Le transport n'est donc probablement pas la seule chose. Cela peut rassembler de nombreux champs qui nécessitent des lectures aléatoires. Un grand nombre de lectures aléatoires peut également provoquer des échecs de cache, et une grande quantité de mémoire requise peut nécessiter que les choses se matérialisent sur le disque et un grand nombre de lignes larges, si une jointure est présente (et il y en a une si TOAST est impliqué) peut nécessiter plus cher joindre des modèles, etc.

La première chose que je voudrais faire est de sélectionner moins de lignes et de voir si cela aide. Si cela fonctionne, vous pouvez essayer d'ajouter plus RAM au serveur aussi, mais je commencerais et verrais où les performances commencent à chuter en raison des changements de plan et des échecs de cache en premier.

14
Chris Travers

Utilisation des Fonctions de taille d'objet de base de données mentionnées ci-dessus:

SELECT primary_key, pg_column_size(tablename.*) FROM tablename;
7