Mesurer la taille d'une ligne de table PostgreSQL


83

J'ai une table PostgreSQL. select *est très lent alors que select idc'est gentil et rapide. Je pense que la taille de la rangée est très grande et que son transport prend du temps, ou peut-être un autre facteur.

J'ai besoin de tous les champs (ou presque tous), donc la sélection d'un 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 n'importe quelle taille. Mais toujours, 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 à l'aide de la ligne de commande Postgres pour m'aider à résoudre ce problème?

En fait ... une des colonnes est de 11 Mo. Cela expliquera je pense. Alors, y a-t-il un moyen de faire length(*)plutôt que simplement length(field)? Je sais que ce ne sont pas des octets, mais j'ai seulement besoin d'une valeur approximative.
Joe le

Réponses:


101

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 fonctions d'accès aux statistiques en bas. (Le module supplémentaire pgstattuple fournit encore plus de fonctions 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 de ce que vous voulez mesurer, exactement.

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

En utilisant une VALUESexpression dans une LATERALsous - requête , pour éviter d'épeler les calculs pour chaque ligne.

Remplacez public.tbl(deux fois) par le nom de votre table, éventuellement qualifié du schéma, pour obtenir une vue compacte des statistiques collectées sur la taille de vos lignes. Vous pouvez envelopper ceci dans une fonction plpgsql pour un usage répété, donner le nom de la table en tant que 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
 carte_visibilité | 0 | 0 octet | 0
 free_space_map | 32768 | 32 kB | 0
 taille_table_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
 ------------------------------ | | |
 nombre_lignes | 483424 | |
 live_tuples | 483424 | |
 dead_tuples | 2677 | |

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

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, qui sont actuellement perdus pour le remplissage de l'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 "tetris de colonne" . Détails:

Considérez également:


Votre extrait de code pré-9.3 génère une division par zéro si la table est vide. En fait, je voulais utiliser la version 9.3+, mais j'ai choisi la mauvaise par erreur et j'ai dû passer quelques heures à la réparer ... Maintenant, je ne peux pas laisser tout ce temps perdre son temps. Remplacez , unnest(val) / ctpar , (LEAST(unnest(val), unnest(val) * ct)) / (ct - 1 + sign(ct))et il ne jettera pas. La justification est que, quand ctest 0, valsera remplacé par 0et ctsera remplacé par 1.
GuiRitter

1
@GuiRitter: Merci de l'avoir signalé. J'ai appliqué une solution plus simple, cependant. Aussi, quelques mises à jour générales tout en y étant - mais la requête reste la même.
Erwin Brandstetter

35

Il est facile d'obtenir une approximation de la taille d'une ligne, y compris le contenu de TOAST , 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;

Ceci est 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 les résultats au format texte, comme le 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 Nrangées "plus grandes dans le texte" de tablename:

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

Excellent moyen d'obtenir rapidement des estimations lorsque vous travaillez avec des données volumineuses (par exemple, la majorité de la taille de la ligne réside dans des colonnes de taille variable stockées en mémoire), bonne idée!
fgblomqvist

résultat sont des octets?
Akmal Salikhov

14

Il y a quelques choses qui pourraient se passer. En général, je doute que la longueur soit le problème proximal. Je suppose que vous avez plutôt 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 la mémoire principale et il est probable que vos champs de texte les plus grands aient été TOASTés ou sortis de la mémoire principale vers une mémoire étendue dans des fichiers séparés. Cela rend votre stockage principal plus rapide (donc, id est en fait plus rapide car moins de pages de disque sont accessibles), mais select * devient plus lent car il y a plus d'entrées / sorties aléatoires.

Si la taille totale de vos lignes est toujours inférieure à 8 000 Ko, vous pouvez essayer de modifier les paramètres de stockage. Je tiens toutefois à vous avertir que l'insertion d'un attribut surdimensionné dans le stockage principal peut poser problème, il est donc préférable de ne pas y toucher si vous n'avez pas à le faire et si vous le faites, définissez des limites appropriées via des contraintes de vérification. Donc, le transport n'est probablement pas la seule chose. Il peut s'agir de rassembler de nombreux champs nécessitant des lectures aléatoires. Un grand nombre de lectures aléatoires peut également entraîner des erreurs de cache, et une grande quantité de mémoire requise peut nécessiter que les choses se matérialisent sur un 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 être plus coûteux rejoindre des modèles, etc.

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


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.