PostgreSQL Antipatterns: сраТаСмся с ΠΎΡ€Π΄Π°ΠΌΠΈ Β«ΠΌΠ΅Ρ€Ρ‚Π²Π΅Ρ†ΠΎΠ²Β»

ΠžΡΠΎΠ±Π΅Π½Π½ΠΎΡΡ‚ΠΈ Ρ€Π°Π±ΠΎΡ‚Ρ‹ Π²Π½ΡƒΡ‚Ρ€Π΅Π½Π½ΠΈΡ… ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌΠΎΠ² PostgreSQL ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡŽΡ‚ Π΅ΠΌΡƒ Π±Ρ‹Ρ‚ΡŒ ΠΎΡ‡Π΅Π½ΡŒ быстрым Π² ΠΎΠ΄Π½ΠΈΡ… ситуация ΠΈ Β«Π½Π΅ ΠΎΡ‡Π΅Π½ΡŒΒ» Π² Π΄Ρ€ΡƒΠ³ΠΈΡ…. БСгодня остановимся Π½Π° классичСском ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ ΠΊΠΎΠ½Ρ„Π»ΠΈΠΊΡ‚Π° ΠΌΠ΅ΠΆΠ΄Ρƒ Ρ‚Π΅ΠΌ, ΠΊΠ°ΠΊ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ Π‘Π£Π‘Π” ΠΈ Ρ‚Π΅ΠΌ, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚ с Π½Π΅ΠΉ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ β€” UPDATE vs ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΡ‹ MVCC.

ΠšΡ€Π°Ρ‚ΠΊΠΎ ΡΡŽΠΆΠ΅Ρ‚ ΠΈΠ· ΠΎΡ‚Π»ΠΈΡ‡Π½ΠΎΠΉ ΡΡ‚Π°Ρ‚ΡŒΠΈ:

Когда строка измСняСтся ΠΊΠΎΠΌΠ°Π½Π΄ΠΎΠΉ UPDATE, фактичСски Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡŽΡ‚ΡΡ Π΄Π²Π΅ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ: DELETE ΠΈ INSERT. Π’ Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ вСрсии строки устанавливаСтся xmax, Ρ€Π°Π²Π½Ρ‹ΠΉ Π½ΠΎΠΌΠ΅Ρ€Ρƒ Ρ‚Ρ€Π°Π½Π·Π°ΠΊΡ†ΠΈΠΈ, Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΠ²ΡˆΠ΅ΠΉ UPDATE. Π—Π°Ρ‚Π΅ΠΌ создаСтся новая вСрсия Ρ‚ΠΎΠΉ ΠΆΠ΅ строки; Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ xmin Ρƒ Π½Π΅Π΅ совпадаСт с Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ΠΌ xmax ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅ΠΉ вСрсии.

Π§Π΅Ρ€Π΅Π· ΠΊΠ°ΠΊΠΎΠ΅-Ρ‚ΠΎ врСмя послС Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΈΡ этой Ρ‚Ρ€Π°Π½Π·Π°ΠΊΡ†ΠΈΠΈ старая ΠΈΠ»ΠΈ новая вСрсии, Π² зависимости ΠΎΡ‚ COMMIT/ROOLBACK, Π±ΡƒΠ΄ΡƒΡ‚ ΠΏΡ€ΠΈΠ·Π½Π°Π½Ρ‹ Β«ΠΌΠ΅Ρ€Ρ‚Π²Ρ‹ΠΌΠΈΒ» (dead tuples) ΠΏΡ€ΠΈ ΠΏΡ€ΠΎΡ…ΠΎΠ΄Π΅ VACUUM ΠΏΠΎ Ρ‚Π°Π±Π»ΠΈΡ†Π΅ ΠΈ Π·Π°Ρ‡ΠΈΡ‰Π΅Π½Ρ‹.

PostgreSQL Antipatterns: сраТаСмся с ΠΎΡ€Π΄Π°ΠΌΠΈ «ΠΌΠ΅Ρ€Ρ‚Π²Π΅Ρ†ΠΎΠ²»

Но это ΠΏΡ€ΠΎΠΈΠ·ΠΎΠΉΠ΄Π΅Ρ‚ Π΄Π°Π»Π΅ΠΊΠΎ Π½Π΅ сразу, Π° Π²ΠΎΡ‚ ΠΏΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ с Β«ΠΌΠ΅Ρ€Ρ‚Π²Π΅Ρ†Π°ΠΌΠΈΒ» ΠΌΠΎΠΆΠ½ΠΎ Π½Π°ΠΆΠΈΡ‚ΡŒ ΠΎΡ‡Π΅Π½ΡŒ быстро β€” ΠΏΡ€ΠΈ ΠΌΠ½ΠΎΠ³ΠΎΠΊΡ€Π°Ρ‚Π½ΠΎΠΌ ΠΈΠ»ΠΈ массовом ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΈ записСй Π² большой Ρ‚Π°Π±Π»ΠΈΡ†Π΅, Π° Ρ‡ΡƒΡ‚ΡŒ ΠΏΠΎΠ·ΠΆΠ΅ ΡΡ‚ΠΎΠ»ΠΊΠ½ΡƒΡ‚ΡŒΡΡ с ситуациСй, Ρ‡Ρ‚ΠΎ ΠΈ VACUUM Π½Π΅ смоТСт ΠΏΠΎΠΌΠΎΡ‡ΡŒ.

#1: I Like To Move It

Допустим, ваш ΠΌΠ΅Ρ‚ΠΎΠ΄ Π½Π° бизнСс-Π»ΠΎΠ³ΠΈΠΊΠ΅ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚ сСбС, ΠΈ Π²Π΄Ρ€ΡƒΠ³ ΠΏΠΎΠ½ΠΈΠΌΠ°Π΅Ρ‚, Ρ‡Ρ‚ΠΎ Π½Π°Π΄ΠΎ Π±Ρ‹ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ ΠΏΠΎΠ»Π΅ X Π² ΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ записи:

UPDATE tbl SET X = <newX> WHERE pk = $1;

ΠŸΠΎΡ‚ΠΎΠΌ, ΠΏΠΎ Ρ…ΠΎΠ΄Ρƒ выполнСния, выясняСт, Ρ‡Ρ‚ΠΎ ΠΏΠΎΠ»Π΅ Y Π½Π°Π΄ΠΎ Π±Ρ‹ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ Ρ‚ΠΎΠΆΠ΅:

UPDATE tbl SET Y = <newY> WHERE pk = $1;

… Π° ΠΏΠΎΡ‚ΠΎΠΌ Π΅Ρ‰Π΅ ΠΈ Z β€” Ρ‡Π΅Π³ΠΎ ΡƒΠΆ ΠΌΠ΅Π»ΠΎΡ‡ΠΈΡ‚ΡŒΡΡ-Ρ‚ΠΎ?

UPDATE tbl SET Z = <newZ> WHERE pk = $1;

Бколько вСрсий этой записи Ρ‚Π΅ΠΏΠ΅Ρ€ΡŒ ΠΈΠΌΠ΅Π΅ΠΌ Π² Π±Π°Π·Π΅? Ага, 4 ΡˆΡ‚ΡƒΠΊΠΈ! Из Π½ΠΈΡ… ΠΎΠ΄Π½Π° Π°ΠΊΡ‚ΡƒΠ°Π»ΡŒΠ½Π°Ρ, Π° 3 Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΈΠ±Ρ€Π°Ρ‚ΡŒ Π·Π° Π²Π°ΠΌΠΈ [auto]VACUUM.

НС Π½Π°Π΄ΠΎ Ρ‚Π°ΠΊ! Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ всСх ΠΏΠΎΠ»Π΅ΠΉ Π·Π° ΠΎΠ΄ΠΈΠ½ запрос β€” ΠΏΠΎΡ‡Ρ‚ΠΈ всСгда Π»ΠΎΠ³ΠΈΠΊΡƒ Ρ€Π°Π±ΠΎΡ‚Ρ‹ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° ΠΌΠΎΠΆΠ½ΠΎ Ρ‚Π°ΠΊ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ:

UPDATE tbl SET X = <newX>, Y = <newY>, Z = <newZ> WHERE pk = $1;

#2: Use IS DISTINCT FROM, Luke!

Π˜Ρ‚Π°ΠΊ, Π²Π°ΠΌ всС-Ρ‚Π°ΠΊΠΈ Π·Π°Ρ…ΠΎΡ‚Π΅Π»ΠΎΡΡŒ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ ΠΌΠ½ΠΎΠ³ΠΎ-ΠΌΠ½ΠΎΠ³ΠΎ записСй Π² Ρ‚Π°Π±Π»ΠΈΡ†Π΅ (Π² Ρ…ΠΎΠ΄Π΅ примСнСния скрипта ΠΈΠ»ΠΈ ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π΅Ρ€Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€). И Π² скрипт Π»Π΅Ρ‚ΠΈΡ‚ Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ Ρ‚Π°ΠΊΠΎΠ΅:

UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2;

ΠŸΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ Π² Ρ‚Π°ΠΊΠΎΠΌ Π²ΠΈΠ΄Π΅ запрос встрСчаСтся достаточно часто ΠΈ ΠΏΠΎΡ‡Ρ‚ΠΈ всСгда Π½Π΅ для заполнСния пустого Π½ΠΎΠ²ΠΎΠ³ΠΎ поля, Π° для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ†ΠΈΠΈ ΠΊΠ°ΠΊΠΈΡ…-Ρ‚ΠΎ ошибок Π² Π΄Π°Π½Π½Ρ‹Ρ…. ΠŸΡ€ΠΈ этом сама ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΡΡ‚ΡŒ ΡƒΠΆΠ΅ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ… Π΄Π°Π½Π½Ρ‹Ρ… Π²ΠΎΠΎΠ±Ρ‰Π΅ Π½Π΅ учитываСтся β€” Π° зря! Π’ΠΎ Π΅ΡΡ‚ΡŒ запись пСрСписываСтся, Π΄Π°ΠΆΠ΅ Ссли Ρ‚Π°ΠΌ Π»Π΅ΠΆΠ°Π»ΠΎ Ρ€ΠΎΠ²Π½ΠΎ Ρ‚ΠΎ, Ρ‡Ρ‚ΠΎ ΠΈ Ρ…ΠΎΡ‚Π΅Π»ΠΎΡΡŒ β€” Π° Π·Π°Ρ‡Π΅ΠΌ? ΠŸΠΎΠΏΡ€Π°Π²ΠΈΠΌ:

UPDATE tbl SET X = <newX> WHERE pk BETWEEN $1 AND $2 AND X IS DISTINCT FROM <newX>;

МногиС Π½Π΅ Π² курсС ΠΏΡ€ΠΎ сущСствованиС Ρ‚Π°ΠΊΠΎΠ³ΠΎ Π·Π°ΠΌΠ΅Ρ‡Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π°, поэтому Π²ΠΎΡ‚ ΡˆΠΏΠ°Ρ€Π³Π°Π»ΠΊΠ° ΠΏΠΎ IS DISTINCT FROM ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΠΌ логичСским ΠΎΠΏΠ΅Ρ€Π°Ρ‚ΠΎΡ€Π°ΠΌ Π² ΠΏΠΎΠΌΠΎΡ‰ΡŒ:
PostgreSQL Antipatterns: сраТаСмся с ΠΎΡ€Π΄Π°ΠΌΠΈ «ΠΌΠ΅Ρ€Ρ‚Π²Π΅Ρ†ΠΎΠ²»
… ΠΈ Π½Π΅ΠΌΠ½ΠΎΠ³ΠΎ ΠΏΡ€ΠΎ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ Π½Π°Π΄ слоТными ROW()-выраТСниями:
PostgreSQL Antipatterns: сраТаСмся с ΠΎΡ€Π΄Π°ΠΌΠΈ «ΠΌΠ΅Ρ€Ρ‚Π²Π΅Ρ†ΠΎΠ²»

#3: А я ΠΌΠΈΠ»ΠΎΠ³ΠΎ ΡƒΠ·Π½Π°ΡŽ по… Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ΅

Π—Π°ΠΏΡƒΡΠΊΠ°ΡŽΡ‚ΡΡ Π΄Π²Π° ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹Ρ… ΠΏΠ°Ρ€Π°Π»Π»Π΅Π»ΡŒΠ½Ρ‹Ρ… процСсса, ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ ΠΈΠ· ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… пытаСтся ΠΏΠΎΠΌΠ΅Ρ‚ΠΈΡ‚ΡŒ Π½Π° записи, Ρ‡Ρ‚ΠΎ ΠΎΠ½Π° находится Β«Π² Ρ€Π°Π±ΠΎΡ‚Π΅Β»:

UPDATE tbl SET processing = TRUE WHERE pk = $1;

Π”Π°ΠΆΠ΅ Ссли эти процСссы ΠΏΡ€Π΅Π΄ΠΌΠ΅Ρ‚Π½ΠΎ Π΄Π΅Π»Π°ΡŽΡ‚ нСзависимыС Π΄Ρ€ΡƒΠ³ ΠΎΡ‚ Π΄Ρ€ΡƒΠ³Π° Π²Π΅Ρ‰ΠΈ, Π½ΠΎ Π² Ρ€Π°ΠΌΠΊΠ°Ρ… ΠΎΠ΄Π½ΠΎΠ³ΠΎ ID, Π½Π° этом запросС Π²Ρ‚ΠΎΡ€ΠΎΠΉ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ «залочится», ΠΏΠΎΠΊΠ° Π½Π΅ закончится пСрвая транзакция.

РСшСниС β„–1: Π·Π°Π΄Π°Ρ‡Π° свСдСна ΠΊ ΠΏΡ€Π΅Π΄Ρ‹Π΄ΡƒΡ‰Π΅ΠΉ

ΠŸΡ€ΠΎΡΡ‚ΠΎ снова Π΄ΠΎΠ±Π°Π²ΠΈΠΌ IS DISTINCT FROM:

UPDATE tbl SET processing = TRUE WHERE pk = $1 AND processing IS DISTINCT FROM TRUE;

Π’ Ρ‚Π°ΠΊΠΎΠΌ Π²ΠΈΠ΄Π΅ Π²Ρ‚ΠΎΡ€ΠΎΠΉ запрос просто Π½ΠΈΡ‡Π΅Π³ΠΎ Π½Π΅ Π±ΡƒΠ΄Π΅Ρ‚ ΠΌΠ΅Π½ΡΡ‚ΡŒ Π² Π±Π°Π·Π΅, Ρ‚Π°ΠΌ ΠΈ Ρ‚Π°ΠΊ ΡƒΠΆΠ΅ «всС ΠΊΠ°ΠΊ Π½Π°Π΄ΠΎΒ» β€” поэтому ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠ° Π½Π΅ Π²ΠΎΠ·Π½ΠΈΠΊΠ½Π΅Ρ‚. Π”Π°Π»ΡŒΡˆΠ΅ Ρ„Π°ΠΊΡ‚ «нСнахоТдСния» записи ΡƒΠΆΠ΅ ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌ Π² ΠΏΡ€ΠΈΠΊΠ»Π°Π΄Π½ΠΎΠΌ Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΠ΅.

РСшСниС β„–2: advisory locks

Π‘ΠΎΠ»ΡŒΡˆΠ°Ρ Ρ‚Π΅ΠΌΠ° для ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ ΡΡ‚Π°Ρ‚ΡŒΠΈ, Π² ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠΉ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ ΠΏΡ€ΠΎ способы ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠΉ ΠΈ Β«Π³Ρ€Π°Π±Π»ΠΈΒ» Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹Ρ… Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΎΠΊ.

РСшСниС β„–3: Π±Π΅Π·[Π΄]ΡƒΠΌΠ½Ρ‹Π΅ Π²Ρ‹Π·ΠΎΠ²Ρ‹

А Π²ΠΎΡ‚ Ρ‚ΠΎΡ‡Π½ΠΎ-Ρ‚ΠΎΡ‡Π½ΠΎ Ρƒ вас Π΄ΠΎΠ»ΠΆΠ½Π° ΠΏΡ€ΠΎΠΈΡΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ одноврСмСнная Ρ€Π°Π±ΠΎΡ‚Π° с ΠΎΠ΄Π½ΠΎΠΉ ΠΈ Ρ‚ΠΎΠΉ ΠΆΠ΅ записью? Или Π²Ρ‹ всС-Ρ‚Π°ΠΊΠΈ накосячили с Π°Π»Π³ΠΎΡ€ΠΈΡ‚ΠΌΠ°ΠΌΠΈ Π²Ρ‹Π·ΠΎΠ²ΠΎΠ² бизнСс-Π»ΠΎΠ³ΠΈΠΊΠΈ со стороны ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€? А Ссли ΠΏΠΎΠ΄ΡƒΠΌΠ°Ρ‚ΡŒ?..

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ: habr.com