7
votes

Structure de données pour stocker un champ de tri pour permettre efficacement des modifications

J'utilise Django et PostgreSQL, mais je ne suis pas absolument attaché au Django Orm s'il existe un meilleur moyen de le faire avec des opérations spécifiques à SQL brut ou à la base de données.

J'ai un modèle qui a besoin de séquentiel commander. Les opérations de recherche récupéreront généralement la liste complète dans l'ordre. L'opération la plus courante sur ces données consiste à déplacer une ligne vers le bas d'une liste, avec un sous-ensemble des éléments intermédiaires bouillonnant pour remplacer l'élément précédent comme celui-ci: p> xxx pré>

En général, le sous-ensemble d'éléments ne sera pas plus d'environ 50 éléments, mais la liste de base peut atteindre des dizaines de milliers d'entrées. P>

Le moyen le plus évident de la mise en œuvre est avec un simple ordre entier. domaine. Cela semble suboptimal. Il nécessite le compromis de la mise en place de la colonne de commande de position non unique, où le non-unicité n'est requis que pour la durée d'une opération de modification. Pour voir cela, imaginez l'opération minimale à l'aide d'un sous-ensemble B: p> xxx pré>

Même si vous avez enregistré la position, à la deuxième ligne, vous avez violé la contrainte d'unicité. De plus, cette méthode rend l'atomicité problématique - votre opération de lecture doit se produire avant l'écriture, au cours de laquelle vos enregistrements pourraient changer. La documentation de traitement par défaut de la transaction par défaut de Django ne résout pas cela, bien que je sache que cela devrait être possible dans le SQL à l'aide du niveau de verrouillage de transaction «Read Lecture répétable». P>

Je cherche des structures de données alternatives qui conviennent à cette question Utilisez le motif de plus près. J'ai regardé cette question pour des idées. p>

Une proposition Il y a la solution de style décimal de dewey, ce qui rend les opérations d'insertion se produisent numériquement entre les valeurs existantes, l'insertion d'un résultat entre B et C : P>

A=1   ->   B=2
B=2   ->   A=2.5
C=3   ->   C=3


2 commentaires

Jeter une prime ici à cause du faible nombre de réponses ...


Salut Paul - Je vois que tu as accepté ma réponse - merci: d. Laquelle des solutions proposées avez-vous décidé d'aller avec et pourquoi?


5 Réponses :


1
votes

Il me semble que votre vrai problème est la nécessité de verrouiller une table pendant la durée d'une transaction. Je ne vois pas immédiatement un bon moyen de résoudre ce problème en une seule opération, d'où le besoin de verrouillage.

La question est de savoir si vous pouvez le faire sous une "voie Django" par opposition à l'utilisation de SQL droit. Recherche "Table de verrouillage de Django" a montré des liens intéressants, y compris Cet extrait , il y a beaucoup de d'autres qui mettent en œuvre un comportement similaire.

Une solution de style Linked-liste lié droite peut être trouvée dans ce Overflow de pile , Il est apparu logique et succinct pour moi, mais à nouveau, il s'agit de deux opérations.

Je suis très curieux d'entendre comment cela se passe et quelle est votre solution finale, assurez-vous de nous tenir au courant!


4 commentaires

La réponse acceptée sur ce poste est plus ou moins ce que je proposais en premier lieu. Je ne pense vraiment pas que ce soit une mise en œuvre du concept de la liste liée cependant. Je conviens que le verrouillage de la table est une partie clé de mon problème, mais je suis toujours vraiment intéressé par de meilleures structures de données pour cela aussi, car je ne sais pas que la numérotation plate va bien augmenter.


Le niveau de verrouillage approprié est "Lecture répétable", qui empêche les données extraites de la modification de la durée de la transaction, sans verrouiller le reste de la table.


"L'optimisation prématurée est la racine de tout Mal!" ;) On dirait que vous avez une limite supérieure à l'esprit, pourquoi ne pas tester l'approche au nombre plat avec 50 000 entrées et voir comment elle échoue? Cela aidera à informer votre décision, car je suis sûr que la mise en œuvre d'une structure de données comportera ses propres compromis coûts / avantages.


Les documents PostgreSQL disent que cela ne fournit que 2 niveaux d'isolement réel. Sérialisable semble être la seule option: postgresql.org/docs/current/static/transaction-iso.html



1
votes

Vous pouvez résoudre le problème de renumérotation en effectuant la colonne de commande comme un entier qui est toujours un nombre pair. Lorsque vous déplacez les données, vous modifiez le champ de commande sur la nouvelle valeur de tri + 1, puis effectuez une mise à jour rapide pour convertir tous les champs d'ordre impaire à même:

update table set sort_order = bitand(sort_order, '0xFFFFFFFE')
where sort_order <> bitand(sort_order, '0xFFFFFFFE')


5 commentaires

Ceci est une solution assez utilisable. Toute commentaires sur la performance de ce processus de deux passes et impair vs et permettant simplement aux champs d'être non uniques et de verrouiller les lignes pendant la transaction?


Il y a trop de variables: DBMS, type d'index, nombre de lignes dans la table,% de lignes modifiées, autres mises à jour dans la même transaction, etc. Vous auriez besoin de le profiler avec de bonnes données d'échantillon. L'étape la plus importante est d'avoir un SGBD qui peut effectuer la mise à jour sans effectuer une analyse de table. Certains SGBD ont du mal à utiliser des index lorsque vous appliquez des fonctions à la colonne indexée.


Premièrement, cette solution ne tient pas compte de l'écart causé par le déplacement de l'article de son ancienne position. Deuxièmement, toute solution utilisant une colonne simple ordre de tri entraînera plusieurs écrivies sur la réorganisation. Utilisation de ce mécanisme à deux passes, vous aurez toujours un certain nombre d'écrires au moins égal à celui du nombre d'enregistrements dans votre champ d'application, ainsi que la modification de l'indice pour ces enregistrements, qui affecteront certainement la performance de la base de données enfin, vous êtes toujours Au besoin de verrouiller la table pour effectuer l'opération Atomic - il n'y a aucun avantage sur votre solution d'origine.


Si votre principale préoccupation est votre contrainte unique, vous pouvez toujours créer l'espace avant de repositionner de la cible. Ive a mis à jour ma réponse pour tenir compte de cette solution.


Matt, aucune lacune n'est nécessaire. Dans l'exemple, 4 entrées sont sélectionnées et leur ordre de tri est tourné à une position avec l'entrée supérieure devenant la dernière entrée. L'astuce met à jour simultanément les champs de 4 commandes de tri simultanément. Consultez ma deuxième réponse avec la table temporaire pour une méthode pour faire cela.



1
votes

Pourquoi ne pas faire un champ de caractère simple d'une certaine longueur comme un maximum de 16 (ou 255) initialement.

Commencez initialement avec l'étiquetage des choses AAA à travers Zzz (qui devrait être 17576 entrées). (Vous pouvez également ajouter 0-9 et les lettres majuscules et les symboles d'une optimisation.)

Comme les éléments sont ajoutés, ils peuvent aller jusqu'au maximum que vous permettez aux «temps de fin» supplémentaires (ZZZA, ZZZAA, ZZZAAA, ZZZAAB, ZZZAAC, ZZZAAD, etc.)

Cela devrait être raisonnable simple à programmer et c'est très similaire au système décimal de Dewey.

Oui, vous devrez le rééquilibrer de temps en temps, mais cela devrait être une simple opération. L'approche la plus simple est deux passes, passe 1 serait de définir la nouvelle étiquette de commande sur '0' (ou tout caractère plus tôt que le premier caractère) suivie de la nouvelle étiquette de la longueur appropriée et l'étape 2 serait de supprimer le ' 0 de l'avant.

Evrieusement, vous pouvez faire la même chose avec des flotteurs et le rééquilibrer régulièrement, c'est juste une variation de cela. L'un des avantages est que la plupart des bases de données vous permettront de définir une taille maximale ridiculement importante pour le champ de caractère, suffisamment grande pour la rendre très improbable que vous manquez de chiffres pour effectuer la commande et que vous le rendez peu probable que vous auriez jamais à modifier le schéma, tout en gaspillant beaucoup d'espace.


0 commentaires

6
votes

5 commentaires

Le module LTREE à Postgres est une suggestion intéressante. Je vais aller jeter un coup d'oeil à ça.


Aussi intéressant que LTREE prend en charge l'indexation B-Tree hors de la boîte.


Verrouiller toute la table est assez indésirable car le système est destiné à prendre en charge de nombreuses mises à jour simultanées.


Solution mise à jour pour supprimer la serrure de la table complète et implémenter les serrures de niveau de ligne. Également ajouté une solution potentielle si vous pouvez attendre 8,5. Donnez-moi votre bounty damnit;)


La réponse continue d'améliorer car la prime est ouverte! L'idée de contrainte différée est une bonne.



4
votes

Une table temporaire et une transaction doivent conserver l'atomicité et la contrainte unique sur ordre de tri. Redémarrez le problème, vous voulez aller de:

update XYZ set sort_order = sort_order + 1
where -- whatever your select criteria are

select * from XYZ
where -- same select criteria
order by sort_order


0 commentaires