Comment copier efficacement des millions de lignes d’une table à l’autre dans Postgresql?

37

J'ai deux tables de base de données. L'un contient des centaines de millions d'enregistrements. Permet d'appeler celui-là history. L'autre est calculé quotidiennement et je souhaite copier tous ses enregistrements dans celui- historyci.

Ce que j'ai fait était de courir:

INSERT INTO history SELECT * FROM daily

Et ça a fait l'affaire pendant un moment, mais ça a commencé à devenir de plus en plus lent au fur et à mesure que le nombre de disques augmentait. Maintenant , j'ai environ 2 millions d' enregistrements qui doivent être copiés à partir dailyde historyen seule opération et prend trop de temps pour terminer.

Existe-t-il un autre moyen plus efficace de copier des données d’une table à l’autre?

Milovan Zogovic
la source

Réponses:

10

Si vous prévoyez de conserver l'historique pendant de longues périodes (plusieurs mois), je vous suggère d'examiner les options de partitionnement. Il peut s'agir d'une partition par jour ou par semaine, etc. Cela dépend également des modèles d'accès de votre table d'historique (exécutez-vous des requêtes qui accèdent aux données à travers des dates? Faites-vous beaucoup d'agrégations, etc.). Consultez les vues matérialisées pour stocker les agrégats / résumés. http://www.postgresql.org/docs/9.3/static/ddl-partitioning.html http://www.postgresql.org/docs/9.3/static/sql-creatematerializedizedview.html

Jayadevan
la source
Merci d'avoir répondu. Cela semble être la seule façon de faire. J'aurais besoin de partitionner les données par mois, ce qui rendrait la réindexation beaucoup plus rapide (la régénération d'index étant un problème ici).
Milovan Zogovic
16

Dump la table au format csv

COPY table TO '/tmp/table.csv' DELIMITER ',';

utilisez la commande COPY, qui est beaucoup plus efficace pour les grandes quantités de données.

COPY table FROM '/tmp/table.csv' DELIMITER ',';

Consultez postgres docs sur http://www.postgresql.org/docs/current/static/sql-copy.html pour plus d'informations.

Fabrizio Mazzoni
la source
1
Il fonctionne toujours très, très lentement ... Peut-être qu'il faut faire quelque chose en reconstruisant un indice aussi énorme? Il y a 160 millions de lignes dans la historytable et nous ajoutons 3 millions de lignes supplémentaires.
Milovan Zogovic
2
Si vous remplissez une table vide ou ajoutez plus de lignes qu'il n'en existe déjà, il est généralement plus efficace de supprimer les index non clusterisés et de les recréer une fois le transfert terminé (sauf en cas d'utilisation active des tables à ce moment-là. )
David Spillett
BTW, est-ce une opération unique ou est-ce quelque chose que vous devez faire régulièrement? Si c'est régulièrement, je suppose que vous créez un déclencheur pour que vous n'ayez pas à traverser cette épreuve à chaque fois.
Fabrizio Mazzoni
@FabrizioMazzoni - Il doit être effectué quotidiennement à une heure précise (en quelque sorte, prendre des instantanés à temps).
Milovan Zogovic
@ DavidSpillett - en effet! La suppression d’index rend l’importation très rapide (voir ma réponse ci-dessus), cependant, la recréation d’indices prend des heures (puisque j’ai 160 millions de lignes dans la base de données).
Milovan Zogovic
14

Le problème était avec les index. La historytable avait 160 millions de lignes indexées. En exécutant l’un COPY FROMou l’ autre, INSERT INTO .. SELECTil fallait beaucoup de temps pour insérer des lignes, mais pour mettre à jour les index. Quand j'ai désactivé les index, il a importé 3M lignes en 10 secondes. Maintenant, je dois trouver un moyen plus rapide de réindexer la grande table.

Milovan Zogovic
la source
3
Avez-vous même besoin d'index sur une table d'historique?
Sherlock
2
Ajoutez l'index en utilisant le mot clé CONCURRENTLY
Akvel le
11

Vous pouvez utiliser l' outil psql , je pourrais être efficace, comme suit,

psql -h ${DAILY_HOST_IP} -p ${PG_PORT} ${DB_NAME} ${USER_NAME} -c "copy daily to stdout " | psql -h ${HISTORY_HOST_IP} -p ${PG_PORT} ${DB_NAME} ${USER_NAME}  -c "copy history from stdin"

Aussi, vous pouvez écrire un script shell.

francs
la source
Excellente solution sans fichier intermédiaire. Très vite aussi, j'ai copié une table de 950 millions de lignes en 1h20 (sans index) entre le système de fichiers normal et le système de fichiers réseau.
Le Droid
C'est vraiment dommage que cela ne puisse pas être fait directement d'une table à l'autre.
Charlie Clark il y a
3

Ce n'est bien sûr pas une réponse exacte à votre question, mais si vous n'avez pas besoin d'accéder à la historytable, vous pouvez également générer un cliché SQL:

pg_dump -h host -p port -w -U user db > dump.sql

Ensuite, on pourrait utiliser un outil comme celui-ci gitpour calculer la différence et la stocker efficacement.

git add dump.sql
git commit -m "temp dump"
git gc --aggressive

Ceci est utile car la plupart des composants d’une base de données ne changeront pas tous les jours. Au lieu de stocker une copie complète pour chaque jour, on peut stocker la différence entre deux jours.

Vous pouvez utiliser un crontabtravail tel que le vidage soit traité chaque jour.

Willem Van Onsem
la source