revise January 2021 transaction articles
Build and deploy / deploy (push) Successful in 14s

This commit is contained in:
2026-07-31 12:13:15 +03:00
parent af3ddc4e88
commit 323d80b188
7 changed files with 926 additions and 1 deletions
@@ -0,0 +1,72 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 1210" role="img" aria-labelledby="title desc">
<title id="title">Диагностика конфликта транзакций по симптому, причине, проверке и действию</title>
<desc id="desc">Вертикальная схема связывает четыре симптома конкурентного конфликта: раздельные чтение и запись, неполный набор строк, блокировку в разном порядке и serialization failure. Для каждого показаны причина, точная проверка и безопасное следующее действие.</desc>
<style>
.bg { fill: #f6f8fb; }
.panel { fill: #ffffff; stroke: #d9e1ec; stroke-width: 2; }
.issue { fill: #fff0f0; stroke: #d9534f; stroke-width: 2; }
.check { fill: #fff8e7; stroke: #c38a17; stroke-width: 2; }
.action { fill: #effaf3; stroke: #2a8f5b; stroke-width: 2; }
.ink { fill: #15263b; }
.sub { fill: #52657b; }
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; }
.title { font-size: 27px; font-weight: 700; }
.lead { font-size: 19px; }
.head { font-size: 23px; font-weight: 700; }
.label { font-size: 19px; font-weight: 700; }
.body { font-size: 18px; }
.small { font-size: 17px; }
</style>
<rect class="bg" width="720" height="1210" rx="26"/>
<rect x="32" y="28" width="656" height="120" rx="18" class="panel"/>
<text x="60" y="70" class="title ink">Конфликт: не угадываем, а сужаем границу</text>
<text x="60" y="105" class="lead sub">Карточка связывает: симптом → причина</text>
<text x="60" y="132" class="lead sub">→ проверка → действие.</text>
<rect x="32" y="182" width="656" height="202" rx="18" class="issue"/>
<text x="60" y="222" class="head ink">1. Оба запроса «успешны», правило нарушено</text>
<text x="60" y="254" class="body ink">Причина: read и write не закрыли один shared invariant.</text>
<rect x="60" y="279" width="282" height="76" rx="10" class="check"/>
<text x="78" y="310" class="label ink">Проверка</text>
<text x="78" y="338" class="small ink">Назвать rows и predicate</text>
<rect x="374" y="279" width="282" height="76" rx="10" class="action"/>
<text x="392" y="310" class="label ink">Действие</text>
<text x="392" y="338" class="small ink">Поместить их в one boundary</text>
<path d="M360 384 L360 420" stroke="#7f91a8" stroke-width="4" marker-end="url(#arrow)"/>
<rect x="32" y="438" width="656" height="202" rx="18" class="issue"/>
<text x="60" y="478" class="head ink">2. FOR UPDATE есть, но scope не совпадает</text>
<text x="60" y="510" class="body ink">Причина: locked only one row, а predicate читает many rows.</text>
<rect x="60" y="535" width="282" height="76" rx="10" class="check"/>
<text x="78" y="566" class="label ink">Проверка</text>
<text x="78" y="594" class="small ink">Сопоставить query и invariant</text>
<rect x="374" y="535" width="282" height="76" rx="10" class="action"/>
<text x="392" y="566" class="label ink">Действие</text>
<text x="392" y="594" class="small ink">Зафиксировать полный row set</text>
<path d="M360 640 L360 676" stroke="#7f91a8" stroke-width="4" marker-end="url(#arrow)"/>
<rect x="32" y="694" width="656" height="202" rx="18" class="issue"/>
<text x="60" y="734" class="head ink">3. Операция ждёт или получила deadlock</text>
<text x="60" y="766" class="body ink">Причина: two transactions ask multiple objects in different order.</text>
<rect x="60" y="791" width="282" height="76" rx="10" class="check"/>
<text x="78" y="822" class="label ink">Проверка</text>
<text x="78" y="850" class="small ink">Записать order и transaction scope</text>
<rect x="374" y="791" width="282" height="76" rx="10" class="action"/>
<text x="392" y="822" class="label ink">Действие</text>
<text x="392" y="850" class="small ink">Один order; retry abort only</text>
<path d="M360 896 L360 932" stroke="#7f91a8" stroke-width="4" marker-end="url(#arrow)"/>
<rect x="32" y="950" width="656" height="202" rx="18" class="issue"/>
<text x="60" y="990" class="head ink">4. Serializable вернул SQLSTATE 40001</text>
<text x="60" y="1022" class="body ink">Причина: engine rejected a dangerous read/write pattern.</text>
<rect x="60" y="1047" width="282" height="76" rx="10" class="check"/>
<text x="78" y="1078" class="label ink">Проверка</text>
<text x="78" y="1106" class="small ink">Не published side effect before commit</text>
<rect x="374" y="1047" width="282" height="76" rx="10" class="action"/>
<text x="392" y="1078" class="label ink">Действие</text>
<text x="392" y="1106" class="small ink">Retry whole transaction</text>
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="7" refY="3" orient="auto"><path d="M0,0 L0,6 L8,3 z" fill="#7f91a8"/></marker>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -0,0 +1,58 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 1180" role="img" aria-labelledby="title desc">
<title id="title">Матрица изоляции PostgreSQL 13 для учебного выбора границы</title>
<desc id="desc">Вертикальная матрица сопоставляет Read Committed, Repeatable Read и Serializable в PostgreSQL 13. Для каждого уровня показаны момент snapshot, основная граница и требование к обработке конфликта. Внизу отмечено, что SELECT FOR UPDATE — отдельный способ ограничить доступ к возвращённым строкам, а не лучший уровень изоляции.</desc>
<style>
.bg { fill: #f6f8fb; }
.panel { fill: #ffffff; stroke: #d9e1ec; stroke-width: 2; }
.blue { fill: #eaf3ff; stroke: #2c6ebd; stroke-width: 2; }
.violet { fill: #f2edff; stroke: #7058b5; stroke-width: 2; }
.green { fill: #effaf3; stroke: #2a8f5b; stroke-width: 2; }
.ink { fill: #15263b; }
.sub { fill: #52657b; }
.accent { fill: #234a78; }
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; }
.title { font-size: 31px; font-weight: 700; }
.lead { font-size: 20px; }
.head { font-size: 25px; font-weight: 700; }
.label { font-size: 20px; font-weight: 700; }
.body { font-size: 19px; }
.small { font-size: 18px; }
.mono { font-family: "SFMono-Regular", Consolas, monospace; font-size: 18px; }
</style>
<rect class="bg" width="720" height="1180" rx="26"/>
<rect x="32" y="28" width="656" height="120" rx="18" class="panel"/>
<text x="60" y="74" class="title ink">PostgreSQL 13: изоляция — не рейтинг</text>
<text x="60" y="108" class="lead sub">Сначала назвать инвариант и конфликт, затем выбрать</text>
<text x="60" y="133" class="lead sub">границу, retry или явную блокировку.</text>
<rect x="32" y="178" width="656" height="218" rx="18" class="blue"/>
<text x="60" y="220" class="head ink">Read Committed — default</text>
<text x="60" y="254" class="body ink">Snapshot: начало каждого statement.</text>
<text x="60" y="286" class="body ink">Сильная сторона: простой predetermined row update.</text>
<text x="60" y="318" class="body ink">Риск: несколько statements видят разные committed state.</text>
<text x="60" y="357" class="small sub">Для cross-row правила нужен дополнительный protocol,</text>
<text x="60" y="381" class="small sub">а не надежда, что два SELECT увидят одно и то же.</text>
<rect x="32" y="420" width="656" height="218" rx="18" class="violet"/>
<text x="60" y="462" class="head ink">Repeatable Read</text>
<text x="60" y="496" class="body ink">Snapshot: первый query или data change в transaction.</text>
<text x="60" y="528" class="body ink">Сильная сторона: стабильное представление для чтений.</text>
<text x="60" y="560" class="body ink">Граница: serialization failure возможен; нужен полный retry.</text>
<text x="60" y="599" class="small sub">В PostgreSQL 13 phantom reads не допускаются,</text>
<text x="60" y="623" class="small sub">но serialization anomaly всё ещё возможна.</text>
<rect x="32" y="662" width="656" height="236" rx="18" class="green"/>
<text x="60" y="704" class="head ink">Serializable</text>
<text x="60" y="738" class="body ink">Committed result эквивалентен некоторому serial order.</text>
<text x="60" y="770" class="body ink">Граница: конфликт может отменить один transaction.</text>
<text x="60" y="802" class="body ink">Обработка: retry whole transaction после SQLSTATE 40001.</text>
<text x="60" y="841" class="small sub">Это не «лучший» уровень: он стоит monitoring и retry,</text>
<text x="60" y="865" class="small sub">а пригодность зависит от операции и её side effects.</text>
<rect x="32" y="926" width="656" height="182" rx="18" fill="#172b45"/>
<text x="60" y="970" class="head" fill="#ffffff">Отдельный инструмент: SELECT ... FOR UPDATE</text>
<text x="60" y="1006" class="body" fill="#dbe8f5">В PostgreSQL он блокирует writers и lockers</text>
<text x="60" y="1035" class="body" fill="#dbe8f5">на возвращённых строках до конца transaction.</text>
<text x="60" y="1072" class="small" fill="#dbe8f5">Он работает только с выбранным scope и protocol всех writers;</text>
<text x="60" y="1098" class="small" fill="#dbe8f5">не заменяет формулировку инварианта или интеграционный test.</text>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

@@ -0,0 +1,74 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 1180" role="img" aria-labelledby="title desc">
<title id="title">Учебная шкала двух операций и границы блокировки</title>
<desc id="desc">Вертикальная схема показывает начальное состояние с двумя дежурными, небезопасное расписание двух операций, нарушающее инвариант, и альтернативное расписание, где вторая операция ждёт границу блокировки, читает новое состояние и отказывается от записи.</desc>
<style>
.bg { fill: #f6f8fb; }
.panel { fill: #ffffff; stroke: #d9e1ec; stroke-width: 2; }
.warn { fill: #fff0f0; stroke: #d9534f; stroke-width: 2; }
.safe { fill: #effaf3; stroke: #2a8f5b; stroke-width: 2; }
.muted { fill: #eaf0f7; stroke: #7f91a8; stroke-width: 2; }
.step { fill: #234a78; }
.red { fill: #c64040; }
.green { fill: #187648; }
.ink { fill: #15263b; }
.sub { fill: #52657b; }
text { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif; }
.title { font-size: 31px; font-weight: 700; }
.lead { font-size: 21px; font-weight: 500; }
.label { font-size: 20px; font-weight: 700; }
.body { font-size: 20px; }
.small { font-size: 18px; }
.mono { font-family: "SFMono-Regular", Consolas, monospace; font-size: 18px; }
</style>
<rect class="bg" width="720" height="1180" rx="26"/>
<rect x="32" y="28" width="656" height="118" rx="18" class="panel"/>
<text x="62" y="74" class="title ink">Граница транзакции: один инвариант</text>
<text x="62" y="109" class="lead sub">Учебный schedule: два запроса хотят снять разных</text>
<text x="62" y="135" class="lead sub">дежурных, но обязан остаться хотя бы один.</text>
<circle cx="62" cy="202" r="23" class="step"/>
<text x="55" y="209" class="label" fill="#ffffff">1</text>
<rect x="102" y="166" width="586" height="106" rx="16" class="muted"/>
<text x="128" y="204" class="label ink">Общее начальное состояние</text>
<text x="128" y="235" class="body sub">Анна = on, Борис = on; activeCount = 2; invariant: ≥ 1</text>
<line x1="62" y1="225" x2="62" y2="322" stroke="#7f91a8" stroke-width="4"/>
<circle cx="62" cy="350" r="23" class="red"/>
<text x="55" y="357" class="label" fill="#ffffff">2</text>
<rect x="102" y="302" width="586" height="300" rx="16" class="warn"/>
<text x="128" y="342" class="label ink">Без общей границы — оба читают 2</text>
<rect x="128" y="366" width="242" height="166" rx="12" fill="#ffffff" stroke="#efb6b4" stroke-width="2"/>
<text x="150" y="403" class="label red">T1: Анна</text>
<text x="150" y="435" class="body ink">read active = 2</text>
<text x="150" y="466" class="body ink">write Анна = off</text>
<text x="150" y="498" class="small sub">commit</text>
<rect x="408" y="366" width="242" height="166" rx="12" fill="#ffffff" stroke="#efb6b4" stroke-width="2"/>
<text x="430" y="403" class="label red">T2: Борис</text>
<text x="430" y="435" class="body ink">read active = 2</text>
<text x="430" y="466" class="body ink">write Борис = off</text>
<text x="430" y="498" class="small sub">commit</text>
<text x="128" y="572" class="body red">Итог: activeCount = 0. Записи разные, инвариант нарушен.</text>
<line x1="62" y1="373" x2="62" y2="660" stroke="#7f91a8" stroke-width="4"/>
<circle cx="62" cy="688" r="23" class="green"/>
<text x="55" y="695" class="label" fill="#ffffff">3</text>
<rect x="102" y="640" width="586" height="364" rx="16" class="safe"/>
<text x="128" y="682" class="label ink">Одна граница для строк инварианта</text>
<text x="128" y="709" class="body sub">Фиксированный порядок: [Анна, Борис].</text>
<text x="128" y="735" class="body sub">Учебная блокировка — не движок БД.</text>
<rect x="128" y="744" width="510" height="57" rx="12" fill="#ffffff" stroke="#b9e0c8" stroke-width="2"/>
<text x="150" y="775" class="body ink">T1 захватывает [Анна, Борис]</text>
<text x="150" y="797" class="small sub">→ читает 2 → Анна = off</text>
<rect x="128" y="819" width="510" height="57" rx="12" fill="#ffffff" stroke="#b9e0c8" stroke-width="2"/>
<text x="150" y="850" class="body ink">T2 просит те же строки</text>
<text x="150" y="872" class="small sub">→ ждёт завершения T1</text>
<rect x="128" y="894" width="510" height="76" rx="12" fill="#ffffff" stroke="#b9e0c8" stroke-width="2"/>
<text x="150" y="923" class="body ink">T2 читает уже 1 → precondition false</text>
<text x="150" y="947" class="small sub">→ rollback без записи</text>
<text x="128" y="994" class="body green">Итог модели: activeCount = 1; инвариант сохранён.</text>
<rect x="32" y="1030" width="656" height="116" rx="18" fill="#172b45"/>
<text x="58" y="1070" class="label" fill="#ffffff">Граница доказательства</text>
<text x="58" y="1101" class="small" fill="#dbe8f5">Схема объясняет только фиксированную in-memory модель.</text>
<text x="58" y="1128" class="small" fill="#dbe8f5">Она не запускала PostgreSQL и не измеряла настоящие wait или locks.</text>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB