同じ値で行を更新すると、実際に行が更新されますか?


28

パフォーマンス関連の質問があります。マイケルという名のユーザーがいるとしましょう。次のクエリを実行します。

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

同じ値に更新されている場合でも、クエリは実際に更新を実行しますか?もしそうなら、どうすればそれを防ぐことができますか?


1
なぜ文を実行し、同時にそれが実行されないと期待するのですか?
マックスヴァーノン

@MaxVernon Ruby on RailsのORMはレコードを更新しないため、PostgreSQLが同じことをしたかどうかに興味がありました。
OneSneakyMofo

1
Ruby on Railsがそれを行っている場合、おそらく行を更新する必要があるかどうかを確認するために最初に選択を行うことをお勧めします。
マックスヴァーノン

回答:


35

Postgres のMVCCモデルにより、SQLの規則に従って、は、句で除外されていないすべてのUPDATEに対して新しい行バージョンを書き込みます。WHERE

これ、パフォーマンスに直接的または間接的に多かれ少なかれ実質的な影響を及ぼします。「空の更新」には、行ごとのコストが他の更新と同じです。他の更新と同様にトリガー(存在する場合)を起動し、WALログに記録する必要があり、テーブルを膨張させるデッド行を生成しVACUUM、他の更新と同様に後の作業を増やします。

インデックスエントリとトーストの列関与列のどれもが変更されていないことができます同じまま、それはすべての更新された行に対して真です。関連:

そのような空の更新を除外することは、ほとんど常に良い考えです(実際に発生する可能性がある場合)。質問でテーブル定義を提供していません(常に良い考えです)。first_nameNULLであると仮定する必要があります(「名」にとっては驚くことではありません)。したがって、クエリはNULLセーフな比較を使用する必要があります

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

first_name IS NULL更新前の場合、テストはfirst_name <> 'Michael'NULLと評価されるため、更新から行を除外します。卑劣なエラー。ただし、列が定義されている場合は、単純な同等性チェックを使用します。これは少し安価だからです。NOT NULL

関連:


1
Indexes entries and TOASTed columns where none of the involved columns are changed can stay the sameしかし、行の新しい場所を指すように更新する必要はありませんか?
dvtan

1
@dtgq:HOT更新ではありません。インデックスは古い場所を指し続けることができ、ヒープフェッチはライブタプルを取得するためにHOTチェーンを走査する必要があります。上記の説明にリンクを追加しました。
アーウィンブランドステッター

1
MVCCは、新しいタプルを作成するためにnoop更新を必要としますか?
-jberryman

@jberryman:わかりません。どちらにしても、新しい質問として質問してください。コンテキストのために、いつでもこれにリンクできます。そして、ここにコメントを残してリンクを張ることができます(そして私の注意を引きます)。
アーウィンブランドステッター

2
@jberryman:プロジェクトがこのようになった理由は実際にはわかりません。それはずっと前に確立されました。しかし、私は仮定し、平等のためのすべての行をチェックして不高価だろうと変わらない行の別々のコード・パスを持っています。より複雑になるトランザクションIDの取り扱い-のための特別なケースrollback、スナップショット処理、ロック管理、WAL、何ではない...
アーウィンBrandstetter

4

ORMは、Ruby on Railのような遅延実行を提供し、レコードを変更済み(または未変更)としてマークし、必要なときまたは呼び出されたときに、変更をデータベースに送信します。

PostgreSQLはデータベースであり、ORMではありません。新しい値がクエリの更新された値と同じかどうかを確認するのに時間がかかると、パフォーマンスが低下します。

したがって、新しい値と同じかどうかに関係なく値を更新します。

これを防ぎたい場合は、彼の答えで提案されたMax Vernonのようなコードを使用できます。


2

where句に単純に追加できます。

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

場合first_nameのように定義されNOT NULLOR first_name IS NULL一部を除去することができます。

条件:

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

(Erwinの答えで)よりエレガントに書くこともできます:

first_name IS DISTINCT FROM 'Michael'

列がNULLになる可能性があるかどうかわからないため、卑劣なバグが発生する可能性があります。
アーウィンブランドステッター

1
@ErwinBrandstetter私は答えを更新していました-それからコメントとあなたの答えを見ました!
ypercubeᵀᴹ

編集のためのおかげで、@ypercube -約コメントをNULL@erwin
マックス・バーノン

1

データベースの観点から

あなたの質問に対する答えはイエスです。更新が行われます。データベースは以前の値をチェックせず、新しい値を設定するだけです。

これはメモリ内で発生するため(コミットが発行された後にのみデータファイルに書き込まれます)、パフォーマンスは問題になりません。

ORMの観点から

通常、データベースの単一の行を表すオブジェクトがあります(それよりもはるかに複雑になる可能性がありますが、単純にしておきましょう)。このオブジェクトはメモリで(アプリサーバーレベルで)管理され、そのオブジェクトの最新のコミットバージョンのみが特定の時点で実際にデータベースに到達します。

それは異なる行動を説明するかもしれません。

さて、貨物船と3Dプリンターを比較しないようにしましょう。貨物船を使用して3Dプリンターを送信できるという事実は、それらの間に何らかの比較があるかもしれないという意味ではありません。

楽しい!

これにより、いくつかの概念が明らかになることを願っています。


4
パフォーマンス問題です。すべての更新はディスク(ログとテーブル)に書き込まれる必要があります。
ypercubeᵀᴹ

実際に使用するRDBMSに依存します。しかし、それらのほとんどはすべての更新をコミットするのではなく、メモリ内で最後にコミットしたブロックのみをコミットします。データベースの単一の行を読み書きすることはありません。ブロックを読み書きして、同じ場所に新しいブロックを配置するためにブロックをフラッシュする必要があるまで、それらをメモリに保持します。メモリ内では、行のすべての変更がディスクに書き込まれるわけではなく、「データベースライタ」プロセスがそのメモリブロックをデータファイルにダンプするように通知されたときのブロックコンテンツのみが書き込まれます。だから、いいえ...アプリケーションがブロックをあまりにも長い間コミットしていない限り、問題ではありません。
シルバリオン

1
問題はPostgresに関するものであり、任意のDBMSに関するものではありません。また、更新をすべて1つずつ書き込む必要はありませんが、データベースへのすべての書き込みをログに書き込む必要があります。永続ストレージに変更が書き込まれていない場合、DBMSはシステムクラッシュからどのように生き残りますか?
ypercubeᵀᴹ

はい、チェックポイント時にもメモリからログに書き込みます。非常に多くの同時ユーザーがいない限り、それはまったく問題になりません。ログもバッチで書き込まれます。私たちはサーバーについて話していると思います。5400RPM HDDを搭載したラップトップでPostgresデータベースについて話している場合、はい...常にパフォーマンスの問題が発生します。したがって、最終的な答えは最初のものです...それはあまりにも多くのものに依存しています。
シルバリオン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.