Requête de sélection avec un décalage de la limite est trop lent
J'ai lu des ressources internet qu'une requête sera lente lorsque le décalage augmente. Mais dans mon cas, je pense que c'est trop lent. Je suis à l'aide de postgres 9.3
Voici la requête (id
est clé primaire):
select * from test_table offset 3900000 limit 100;
Il me renvoie des données dans autour de 10 seconds
. Et je pense que c'est trop lent. J'ai autour de 4 million
enregistrements dans la table. Taille globale de la base de données est 23GB
.
Configuration de la Machine:
RAM: 12 GB
CPU: 2.30 GHz
Core: 10
Quelques valeurs de postgresql.conf
fichier que j'ai modifié sont comme ci-dessous. D'autres sont par défaut.
shared_buffers = 2048MB
temp_buffers = 512MB
work_mem = 1024MB
maintenance_work_mem = 256MB
dynamic_shared_memory_type = posix
default_statistics_target = 10000
autovacuum = on
enable_seqscan = off ## its not making any effect as I can see from Analyze doing seq-scan
En dehors de ces j'ai aussi essayé en changeant les valeurs de random_page_cost = 2.0
et cpu_index_tuple_cost = 0.0005
et le résultat est le même.
Explain (analyze, buffers)
résultat de la requête est comme ci-dessous:
"Limit (cost=10000443876.02..10000443887.40 rows=100 width=1034) (actual time=12793.975..12794.292 rows=100 loops=1)"
" Buffers: shared hit=26820 read=378984"
" -> Seq Scan on test_table (cost=10000000000.00..10000467477.70 rows=4107370 width=1034) (actual time=0.008..9036.776 rows=3900100 loops=1)"
" Buffers: shared hit=26820 read=378984"
"Planning time: 0.136 ms"
"Execution time: 12794.461 ms"
Comment les gens partout dans le monde négocie avec ce problème dans postgres? Toute autre solution sera utile pour moi.
Mise à JOUR:: Ajoutant order by id
(essayé avec d'autres colonne indexée) et voici l'expliquer:
"Limit (cost=506165.06..506178.04 rows=100 width=1034) (actual time=15691.132..15691.494 rows=100 loops=1)"
" Buffers: shared hit=110813 read=415344"
" -> Index Scan using test_table_pkey on test_table (cost=0.43..533078.74 rows=4107370 width=1034) (actual time=38.264..11535.005 rows=3900100 loops=1)"
" Buffers: shared hit=110813 read=415344"
"Planning time: 0.219 ms"
"Execution time: 15691.660 ms"
[indépendantes]
work_mem = 1024MB
est probablement trop élevé, default_statistics_target = 10000
est beaucoup trop élevé pour une utilisation générale. autovacuum = off
n'est pas nécessaire et dangereux. Combien de temps avez-vous été courir avec autovacuum?Je
autovacuum
a toujours été. J'ai mis cette off
pour l'expérimentation avant l'exécution de l'expliquer.J'ai toujours une commande par (last_update_date) à la fin de ma requête. Pour des raisons de simplicité, je l'ai enlevé qu'à partir de la question.
Retrait des choses est de ne pas ajouter plus de simplicité, mais ajouter de la confusion. S'il vous plaît montrer le réel de la requête. (en plus de la définition de la table: c'est le pk {id, update_date} ? BTW: une semblable requête s'exécute ici dans
Total runtime: 12.395 ms
Vous devez avoir une table différente de la structure de ce que je peux lire à partir de votre question..
OriginalL'auteur Sabuj Hassan | 2014-10-29
Vous devez vous connecter pour publier un commentaire.
C'est lent parce qu'il a besoin pour localiser le haut
offset
lignes et analyse les 100 prochaines. Aucune somme d'optimisation changera que lorsque vous faites affaire avec d'énormes décalages.C'est parce que votre requête littéralement instruire le moteur DB à visiter, beaucoup de lignes en utilisant
offset 3900000
-- ce qui est de 3,9 M lignes. Options pour accélérer les choses un peu, ne sont pas nombreux.Super-rapide de RAM, Ssd, etc. aidera. Mais vous aurez tout à gagner par un facteur constant en agissant de la sorte, c'est simplement de remettre les choses à la route jusqu'à ce que vous atteindre une plus grande assez de décalage.
Assurer le tableau s'inscrit dans la mémoire, avec beaucoup plus de rechange contribuera également par un plus grand facteur constant -- à l'exception de la première fois. Mais cela peut ne pas être possible avec une assez grande table ou d'un index.
Assurer que vous êtes en train de faire de l'indice-seulement des analyses de travailler à une mesure. (Voir velis de réponse, il a beaucoup de mérite.) Le problème ici est que, à toutes fins pratiques, vous pouvez penser à un index d'une table de stockage d'un emplacement de disque et les champs indexés. (C'est plus optimisé que ça, mais c'est une première approximation raisonnable.) Avec assez de lignes, vous aurez toujours être en cours d'exécution dans problèmes avec une plus grande assez de décalage.
Essayer de stocker et de conserver la position exacte de la ligne, est lié à une approche coûteuse aussi.(Ceci est suggéré par ex. benjist.) Bien que techniquement possible, il souffre de limitations similaires à celles découlant de l'utilisation des MPTT avec une structure en arbre: vous allez acquérir de manière significative sur le lit mais finira par l'excès de temps d'écriture lorsqu'un nœud est inséré, mis à jour ou supprimé de telle manière que de gros morceaux de données doit être mise à jour à côté.
Comme je l'espère, de plus en plus clair, il n'y a pas une vraie magie puce lorsque vous traitez avec des décalages de cette dimension. Il est souvent préférable de s'adresser à d'autres approches.
Si vous êtes à la pagination en fonction de l'ID (ou un champ de date, ou de toute autre indexables ensemble de champs), un potentiel de truc (utilisé par blogspot, par exemple) serait de rendre votre requête commencer à un point arbitraire dans l'index.
Mettre une autre manière, au lieu de:
Faire quelque chose comme:
De cette façon, vous gardez une trace de l'endroit où vous êtes dans votre index, et la requête devient très vite parce qu'il peut vous rendre directement au point de départ correct sans labour par une foule de lignes:
Naturellement, vous perdez la capacité de sauter, par exemple la page 3000. Mais donner ce quelque honnête de la pensée: à quand remonte la dernière fois que vous avez sauté à un grand numéro de page sur un site au lieu d'aller tout droit pour son mensuel des archives ou à l'aide de sa zone de recherche?
Si vous êtes à la pagination mais que vous voulez garder le décalage de page par tous les moyens, encore une autre approche consiste à interdire l'usage du plus grand nombre de pages. Il n'est pas idiot: c'est ce que Google est en train de faire avec les résultats de la recherche. Lors de l'exécution d'une requête de recherche, Google vous donne une estimation du nombre de résultats (vous pouvez obtenir un nombre raisonnable à l'aide de
explain
), et ensuite vous permettra de sourcils haut à quelques milliers de résultats -- rien de plus. Entre autres choses, ils le font pour des raisons de performance -- précisément celui que vous êtes en cours d'exécution dans.En effet. Le premier paragraphe stipule très clairement, je l'espère. Il y a, fondamentalement, n'est pas une bonne option dans le cas général. La seconde commence par: "Si vous êtes à la pagination". Si l'OP est un peu différente de scénario, quelque chose d'équivalent pourrait s'appliquer à lui et cette réponse sera je l'espère le mettre sur la bonne voie, si il y est. Si non, c'est une cause perdue dans mon expérience: aucune quantité d'optimisation sera de rendre la lecture de 4M lignes -- dans n'importe quel ordre -- toujours très rapide.
merci. Je l'ai essayé avant. Mais lorsque l'utilisateur veut accéder à une certaine page(c'est à dire 590 page) ce cas, il n'est pas de m'aider.
Pour ce genre de cas d'utilisation, il n'y a vraiment pas beaucoup que vous pouvez faire... Un énorme décalage rendre la base de données du moteur de la charrue à travers une foule de lignes avant de partir à la sortie de ceux que vous cherchez, et aucun montant de vouloir, prier ou espèrent en faire le moteur de base de magie de lire moins de lignes: vous avez littéralement charger à lire beaucoup d'entre eux. Super-rapide de matériel, ou de s'assurer que vous êtes en train de faire de l'indice seule analyses, pourrait sans doute améliorer les choses un peu; mais pas de façon importante. Assurer le tableau s'inscrit dans la mémoire, avec beaucoup plus de rechange, d'améliorer les choses, bien sûr.
Pouvez vous s'il vous plaît mettre à jour votre réponse et écrire une ligne "Énorme décalage saut est lent".
OriginalL'auteur Denis de Bernardy
J'ai upvoted Denis de la réponse, mais d'ajouter une suggestion de moi-même, peut-être, il peut être de quelque avantage en termes de performance spécifique à votre cas d'utilisation:
En supposant que le tableau n'est pas
test_table
, mais un énorme composé de requête, éventuellement avec plusieurs jointures. Vous pourriez d'abord déterminer le démarrage nécessaire id:Ce devrait être beaucoup plus rapide que l'original de la requête car elle ne nécessite que de l'analyse de l'indice par rapport à la totalité de la table. L'obtention de cette id puis ouvre un index de recherche option fetch:
En fait, il devrait être beaucoup plus rapide que 2 fois. Même si aucune info sur le nombre d'enregistrement est stocké dans l'index des pages, index des enregistrements sont 2 colonne (int), tables réelles probablement contenir beaucoup plus de colonnes, souvent avec de grands champs varchar.
OriginalL'auteur velis
Vous n'avez pas dit si vos données sont principalement en lecture seule ou mis à jour souvent. Si vous pouvez gérer pour créer votre table à la fois, et seulement de la mettre à jour chaque maintenant et puis (disons toutes les quelques minutes) de votre problème facile à résoudre:
offset_id
pour toutes les lignes suivantes. En fonction de la taille de la table et de la fréquence des lignes supprimées, cela peut signifier beaucoup de table de l'écrit.Assurez-vous. C'est pourquoi j'ai dit qu'il doit toujours être fait "pour l'ensemble de votre jeu de données" - si c'est possible à tout pour ses cas d'utilisation. Il n'a pas dit.
OriginalL'auteur benjist
Cette façon, vous obtenez les lignes de semi-ordre aléatoire. Vous n'êtes pas trier les résultats dans une requête, donc comme un résultat, vous obtenez les données stockées dans les fichiers. Le problème est que lorsque vous mettez à jour les lignes, l'ordre peut changer.
Pour corriger cela, vous devez ajouter
order by
à la requête. De cette façon, la requête renvoie les lignes dans le même ordre. Qui plus est, alors il sera en mesure d'utiliser un indice de vitesse de la requête.Alors deux choses: d'ajouter un index, ajouter
order by
à la requête. Les deux sur la même colonne. Si vous voulez utiliser l'id de la colonne, puis de ne pas ajouter de l'index, il suffit de modifier la requête pour quelque chose comme:id
estPK
et essayéorder by
avec elle. Pas de travail. J'ai mis à jour ma question. Aussi essayé avec une autre colonne indexéelast_update_date
et le résultat est le même.De retour d'un ordre résultat sera évidemment le rendre plus lent, bien qu'il fait sens pour la pagination, si c'est ce que l'OP veut.
OriginalL'auteur Szymon Lipiński
Tout d'abord, vous devez définir limit et offset avec la clause order by ou vous obtiendrez incompatible résultat.
Pour accélérer la requête, vous pouvez avoir un indice calculé, mais seulement pour ces condition :
Voici comment Vous pouvez le faire :
create or replace function id_pos (id) returns bigint
as 'select count(id) from test_table where id <= $1;'
language sql immutable;
create index table_by_pos on test_table using btree(id_pos(id));
Voici comment Vous l'appelez (décalage de 3900000 limite de 100):
select * from test_table where id_pos(id) >= 3900000 and sales_pos(day) < 3900100;
De cette façon, la requête ne sera pas calculer la 3900000 de décalage de données, mais seulement de calculer les 100 de données, ce qui rend beaucoup plus rapide.
Veuillez noter que les 2 conditions où cette approche peut avoir lieu, ou la position va changer.
Je suis en utilisant quelque chose comme cela pour envoyer des données en parallèle à un cloud basé sur l'outil de visualisation qui permet seulement d'Ajouter ou de Remplacer des ensembles de données. - Je reconstruire la fonction row_number dans une vue matérialisée une fois par jour, puis d'envoyer des morceaux de lignes where ID > previous_id LIMITE de 200 000. J'ai utilisé des plages de dates avant, mais je suis tombé sur des données qui ont eu extrêmement peu de données pour une période de temps, puis des périodes où il y avait beaucoup trop de lignes à envoyer par requête. J'ai besoin d'envoyer TOUTES les données dans la table, mais faire en sorte que je ne pas envoyer trop de lignes à la fois.
OriginalL'auteur Soni Harriz
Je ne connais pas tous les détails de vos données, mais 4 millions de lignes peut être un peu lourd. Si il y a un moyen raisonnable d'éclat de la table et de essentiellement de le diviser en petites tables il pourrait être bénéfique.
Pour expliquer cela, prenons un exemple. disons que j'ai une base de données où j'ai une table appelée survey_answer, et il devient très grand et très lent. Maintenant, disons que ces réponses à l'enquête proviennent tous d'un groupe distinct de clients (et j'ai aussi une table des clients de garder la trace de ces clients). Puis quelque chose que je pourrais faire c'est que je pourrait faire en sorte que j'ai une table appelée survey_answer qui n'ont pas des données, mais est un parent de la table, et il a un tas d'enfants les tables qui contiennent les données de la suivre le format de nommage survey_answer_<clientid>, ce qui signifie que j'aurais enfant tables survey_answer_1, survey_answer_2, etc., une pour chaque client. Alors quand j'ai eu besoin de sélectionner des données pour le client, je voudrais utiliser cette table. Si j'avais besoin pour sélectionner les données à travers tous les clients, je peux sélectionner à partir du parent survey_answer table, mais ce sera lent. Mais pour l'obtention de données pour un client particulier, qui est ce que j'ai surtout le faire, alors il serait rapide.
C'est un exemple de la façon de briser les données, et il y a beaucoup d'autres. Un autre exemple serait si mon survey_answer table ne se casse pas facilement par le client, mais au lieu de cela je sais que je suis généralement seulement d'accéder à des données sur une période d'une année de temps à la fois, alors je pourrais potentiellement d'enfants, tables à partir d'année, comme survey_answer_2014, survey_answer_2013, etc. Alors si je sais que je ne vais pas avoir accès à plus d'un an, à un moment, je n'ai vraiment besoin d'accéder à peut-être deux de mes enfants tables pour obtenir toutes les données dont j'ai besoin.
Dans votre cas, tout ce que j'ai donné, c'est peut-être l'id. Nous pouvons diviser par qui (bien que peut-être pas idéal). Disons que nous avons le briser, de sorte qu'il n'y a environ 1000000 de lignes par table. Donc, notre enfant les tables seraient test_table_0000001_1000000, test_table_1000001_2000000, test_table_2000001_3000000, test_table_3000001_4000000, etc. Donc, au lieu d'introduire un décalage de 3900000, vous devriez faire un peu de mathématiques de première et de déterminer que la table que vous voulez, c'est le tableau test_table_3000001_4000000 avec un décalage de 900000 à la place. Donc quelque chose comme:
Maintenant, si la fragmentation de la table est hors de question, vous pourriez être en mesure d'utiliser partiellement les indices de faire quelque chose de similaire, mais encore une fois, je vous recommande de fragmentation, d'abord. En savoir plus sur partielle index ici.
J'espère que ça aide. (Aussi, je suis d'accord avec Szymon Guz que vous souhaitez une COMMANDE PAR).
Edit: Noter que si vous avez besoin de supprimer des lignes ou des sélectivement exclure les lignes avant d'obtenir votre résultat de 100, puis de fragmentation par id va devenir très difficiles à traiter (comme l'a souligné Denis; et la fragmentation par l'id n'est pas l'idéal pour commencer). Mais si votre "juste" de la pagination des données, et de vous seulement d'insérer ou de modifier (pas une chose commune, mais il ne se passe; les journaux viennent à l'esprit), puis la fragmentation par id peut être fait de manière raisonnable (bien que j'avais toujours choisir quelque chose d'autre à tesson).
Dépend de la façon dont la base de données est gérée. Il y a plein de cas de figure là où les données ne sont jamais supprimés (de sorte que l'histoire peut être tenue), mais plutôt qu'il est "inactivé" par la modification d'une valeur dans une colonne. Je précise aussi que la fragmentation par l'id n'est pas l'idéal. Fragment par des données plus utiles si elles sont disponibles. J'ai juste travaillé hors de ce peu d'info a été donnée.
Bien sûr, si vous essayez de paginer le 3900000th+ en direct des lignes, il n'y a toujours pas de garantie, il va être dans une partition. Si quoi que ce soit, ça rend les choses pires, parce qu'alors vous devez lire chaque ligne ou avoir un plus grand indice, pour ignorer non-live lignes.
Voudriez-vous me retirer la partie au sujet de la fragmentation sur l'id? La fragmentation est encore très valable à ce sujet, surtout si vous pouvez utiliser les données pertinentes, telles que de mes deux premiers exemples (identification du client et par an). La fragmentation par id je l'ai dit comme un mauvais choix de l'aller, mais si il n'y a rien d'autre y a des façons de le faire fonctionner (et il serait sans doute plus complexe que ce que j'ai mis, mais il donne un début). Mais il est préférable de trouver de réelle logique.
Je crois que je suis avec le dernier commentaire est jusqu'à ce que je sais pourquoi nous sommes de la commande par id, une bonne solution peut pas être mis dehors. généralement, un id est juste un identifiant unique (qui peut parfois être rattachés à une séquence et en tant que tel donner une idée de l'ordre d'insertion, si vous avez de la chance) et en tant que tel, il n'est généralement préférable de ne pas l'utiliser pour la commande. Plutôt quelque chose comme la date ou le nom ou le cote ou similaires doivent être utilisés pour l'ordre, et qui donne quelque chose de mieux à tesson. Je ne peux pas aller plus loin avec l'exemple artificiel (qui ne me donne un id).
OriginalL'auteur Trevor Young
Que diriez-vous si paginer basé sur les numéros au lieu de décalage/limite?
La requête suivante donnera des Id qui se sont séparés de tous les enregistrements en morceaux de taille
per_page
. Il ne dépend pas de dossiers ont été supprimés ou nonAvec ces from_IDs vous pouvez ajouter des liens à la page. Itérer sur :from_ids avec index et ajouter le lien suivant vers la page:
Lorsque l'utilisateur visite la page récupérer des enregistrements avec l'ID qui est plus grand que ce qui est demandé :from_id:
Pour le premier lien de la page avec
from_id=0
travailleraOriginalL'auteur Hirurg103
vous pouvez l'optimiser en deux étapes
D'abord obtenir le maximum d'id de 3900000 enregistrements
select max(id) (select id from test_table order by id limit 3900000);
Ensuite utiliser ces maximale de la carte d'identité pour entrer dans les 100 enregistrements.
select * from test_table id > {max id from previous step) order by id limit 100 ;
Il sera plus rapide que les deux requêtes fera analyse d'index par id.
OriginalL'auteur Manvendra Jina