La mise à jour d'une ligne avec la même valeur met-elle réellement à jour la ligne?

28

J'ai une question liée aux performances. Disons que j'ai un utilisateur avec le prénom Michael. Prenez la requête suivante:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123

La requête exécutera-t-elle réellement la mise à jour, même si elle est mise à jour à la même valeur? Si oui, comment puis-je empêcher que cela se produise?

OneSneakyMofo
la source
1
Pourquoi voudriez-vous exécuter une instruction et vous attendre simultanément à ce qu'elle ne s'exécute pas?
Max Vernon
@MaxVernon Ruby on Rails 'ORM ne met pas à jour l'enregistrement donc j'étais curieux de savoir si PostgreSQL faisait la même chose.
OneSneakyMofo
1
Je dirais que si Ruby on Rails fait cela, il fait probablement d'abord une sélection pour voir si la ligne a besoin d'une mise à jour.
Max Vernon
x-posté à SO: stackoverflow.com/q/33156712/939860
Erwin Brandstetter

Réponses:

35

En raison du modèle MVCC de Postgres et conformément aux règles de SQL, an UPDATEécrit une nouvelle version de ligne pour chaque ligne qui n'est pas exclue dans la WHEREclause.

Cela n'ont un impact plus ou moins important sur les performances, directement et indirectement. Les «mises à jour vides» ont le même coût par ligne que toute autre mise à jour. Ils déclenchent des déclencheurs (s'ils sont présents) comme toute autre mise à jour, ils doivent être journalisés WAL et ils produisent des lignes mortes gonflant la table et provoquant plus de travail pour plus tard comme toute autre mise à jour.VACUUM

Les entrées d'index et les colonnes TOASTed où aucune des colonnes impliquées n'est modifiée peuvent rester les mêmes, mais cela est vrai pour toute ligne mise à jour. En relation:

C'est presque toujours une bonne idée d'exclure ces mises à jour vides (quand il y a une chance réelle que cela se produise). Vous n'avez pas fourni de définition de tableau dans votre question (ce qui est toujours une bonne idée). Nous devons supposer qu'il first_namepeut être NULL (ce qui ne serait pas surprenant pour un "prénom"), donc la requête doit utiliser une comparaison sûre NULL :

UPDATE users
SET    first_name = 'Michael'
WHERE  id = 123
AND   first_name IS DISTINCT FROM 'Michael';

Si first_name IS NULLavant la mise à jour, un test avec juste first_name <> 'Michael'serait évalué à NULL et exclurait ainsi la ligne de la mise à jour. Erreur sournoise. Si la colonne est définieNOT NULL , utilisez la simple vérification d'égalité, car c'est un peu moins cher.

En relation:

Erwin Brandstetter
la source
1
Indexes entries and TOASTed columns where none of the involved columns are changed can stay the sameMais ne devraient-ils pas être mis à jour pour pointer vers le nouvel emplacement de la ligne?
dvtan
1
@dtgq: Pas avec les mises à jour HOT, où l'index peut continuer à pointer vers l'ancien emplacement, et les récupérations de tas doivent traverser la chaîne HOT pour obtenir le tuple en direct. J'ai ajouté des liens vers plus d'explications ci-dessus.
Erwin Brandstetter
1
Qu'en est-il MVCC appelle une mise à jour Noop pour écrire un nouveau tuple?
jberryman
@jberryman: Je ne suis pas sûr de comprendre. Dans tous les cas, posez votre question en tant que nouvelle question . Vous pouvez toujours lier à celui-ci pour le contexte. Et vous pouvez laisser un commentaire ici pour créer un lien (et attirer mon attention).
Erwin Brandstetter
2
@jberryman: Je ne connais pas vraiment les raisons pour lesquelles le projet s'est déroulé de cette façon. Cela a été établi il y a longtemps. Mais je suppose qu'il serait inutilement coûteux de vérifier l'égalité de chaque ligne et d'avoir un chemin de code distinct pour les lignes inchangées. La gestion des ID de transaction serait plus compliquée - boîtier spécial pour rollback, gestion des instantanés, gestion des verrous, WAL, et quoi d'autre ...
Erwin Brandstetter
4

Les ORM comme Ruby on Rail proposent une exécution différée qui marque un enregistrement comme modifié (ou non), puis lorsque cela est nécessaire ou appelé, puis soumettez la modification à la base de données.

PostgreSQL est une base de données et non un ORM. Cela aurait diminué les performances s'il avait fallu du temps pour vérifier si une nouvelle valeur était la même que la valeur mise à jour dans votre requête.

Il mettra donc à jour la valeur, qu'elle soit identique ou non à la nouvelle valeur.

Si vous souhaitez empêcher cela, vous pouvez utiliser du code comme Max Vernon l'a suggéré dans sa réponse.

Thronk
la source
2

Vous pouvez simplement ajouter à la whereclause:

UPDATE users
SET first_name = 'Michael'
WHERE users.id = 123
    AND (first_name <> 'Michael' OR first_name IS NULL);

Si first_nameest défini comme NOT NULL, la OR first_name IS NULLpièce peut être supprimée.

La condition:

(first_name <> 'Michael' OR first_name IS NULL)

peut également être écrit plus élégamment comme (dans la réponse d'Erwin):

first_name IS DISTINCT FROM 'Michael'
Max Vernon
la source
Ne pas savoir si la colonne peut être NULL, cela pourrait introduire un bug sournois.
Erwin Brandstetter
1
@ErwinBrandstetter Je mettais à jour la réponse - alors j'ai vu le commentaire et votre réponse!
ypercubeᵀᴹ
merci pour le montage, @ypercube - et pour le commentaire sur NULL@erwin
Max Vernon
1

Du point de vue de la base de données

La réponse à ta question est oui. La mise à jour aura lieu. La base de données ne vérifie pas la valeur précédente, elle définit uniquement la nouvelle valeur.

Comme cela se produit dans la mémoire (et ne sera écrit dans les fichiers de données qu'une fois la validation émise), les performances ne seront pas un problème.

Du point de vue ORM

Normalement, vous aurez un objet représentant une seule ligne de la base de données (il peut être beaucoup plus complexe que cela, mais restons simples). Cet objet est géré en mémoire (au niveau du serveur d'applications) et seule la dernière version validée de cet objet parviendra réellement à la base de données à un certain moment.

Cela peut expliquer les différents comportements.

Maintenant, ne comparons pas un cargo avec une imprimante 3D. Le fait que vous puissiez envoyer des imprimantes 3D à l'aide de cargos ne signifie pas qu'il puisse y avoir une sorte de comparaison entre elles.

Prendre plaisir!

J'espère que cela a clarifié certains concepts.

Silvarion
la source
4
La performance est un problème. Chaque mise à jour doit être écrite sur disque (le journal et le tableau).
ypercubeᵀᴹ
Cela dépendra du SGBDR réel que vous utilisez. Mais la plupart d'entre eux ne valident pas chaque mise à jour, mais uniquement le dernier bloc validé qu'ils ont en mémoire. Vous ne lisez ou n'écrivez jamais une seule ligne dans une base de données. Vous lisez / écrivez des blocs et les gardez en mémoire jusqu'à ce que vous deviez les vider pour mettre un nouveau bloc au même endroit. En mémoire, toutes les modifications consécutives ne seront pas écrites sur le disque, mais uniquement le contenu du bloc lorsque le processus "écrivain de base de données" est signalé pour vider ce bloc de mémoire dans un fichier de données. Donc, non ... Ce n'est pas un problème à moins que votre application ne retienne le bloc trop longtemps.
Silvarion du
1
la question concerne Postgres, pas tout SGBD arbitraire. Et bien que les mises à jour ne doivent pas toutes être écrites une par une, chaque écriture dans la base de données doit être écrite dans le journal. Si aucune modification n'est écrite sur un stockage persistant, comment le SGBD survivra-t-il à un plantage du système?
ypercubeᵀᴹ
Oui, il écrit dans les journaux, de la mémoire également lors des points de contrôle. À moins que vous n'ayez un nombre énorme d'utilisateurs simultanés, cela ne devrait pas du tout être un problème. Les journaux sont également écrits par lots. Je pense que nous parlons de serveurs. Si vous parlez d'une base de données Postgres dans un ordinateur portable avec un disque dur à 5400 tr / min, oui ... vous aurez toujours des problèmes de performances. Donc, la réponse finale serait la première ... Cela dépend de trop de choses.
Silvarion