<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Concurrency-Control on IT Comparison</title><link>https://comparison.metacog.co.kr/tags/concurrency-control/</link><description>Recent content in Concurrency-Control on IT Comparison</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sun, 06 Sep 2026 09:49:36 +0900</lastBuildDate><atom:link href="https://comparison.metacog.co.kr/tags/concurrency-control/index.xml" rel="self" type="application/rss+xml"/><item><title>Optimistic vs Pessimistic Locking: Detect-at-Commit vs Lock-Before-Access</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-optimistic-vs-pessimistic-locking-detect-at-commit-vs-lock-b/</link><pubDate>Sun, 06 Sep 2026 09:49:36 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-optimistic-vs-pessimistic-locking-detect-at-commit-vs-lock-b/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Both are concurrency-control strategies for preventing lost updates when multiple transactions touch the same data, but they differ in when they deal with conflict. Optimistic locking assumes collisions are rare and only performs a &lt;strong class="kw"&gt;version check&lt;/strong&gt; at commit time, while pessimistic locking assumes collisions are likely and takes an &lt;strong class="kw"&gt;exclusive lock&lt;/strong&gt; before any read or write proceeds.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;
&lt;text x="160" y="28" font-size="18" font-weight="bold" text-anchor="middle" style="fill:var(--primary)"&gt;Optimistic&lt;/text&gt;
&lt;text x="480" y="28" font-size="18" font-weight="bold" text-anchor="middle" style="fill:var(--primary)"&gt;Pessimistic&lt;/text&gt;
&lt;line x1="320" y1="45" x2="320" y2="335" stroke-width="1" stroke-dasharray="4,4" style="stroke:var(--border)"/&gt;
&lt;rect x="115" y="55" width="90" height="38" rx="4" stroke-width="1.5" style="fill:var(--compare-a-soft);stroke:var(--compare-a)"/&gt;
&lt;text x="160" y="79" font-size="13" text-anchor="middle" style="fill:var(--content)"&gt;Record (v1)&lt;/text&gt;
&lt;text x="160" y="105" font-size="10" text-anchor="middle" style="fill:var(--secondary)"&gt;no lock taken&lt;/text&gt;
&lt;line x1="140" y1="93" x2="75" y2="140" stroke-width="1.5" style="stroke:var(--compare-a)"/&gt;
&lt;line x1="180" y1="93" x2="245" y2="140" stroke-width="1.5" style="stroke:var(--compare-a)"/&gt;
&lt;rect x="30" y="140" width="90" height="40" rx="4" stroke-width="1.5" style="fill:var(--compare-a-soft);stroke:var(--compare-a)"/&gt;
&lt;text x="75" y="165" font-size="13" text-anchor="middle" style="fill:var(--content)"&gt;Txn A&lt;/text&gt;
&lt;rect x="200" y="140" width="90" height="40" rx="4" stroke-width="1.5" style="fill:var(--compare-a-soft);stroke:var(--compare-a)"/&gt;
&lt;text x="245" y="165" font-size="13" text-anchor="middle" style="fill:var(--content)"&gt;Txn B&lt;/text&gt;
&lt;line x1="75" y1="180" x2="75" y2="225" stroke-width="1.5" style="stroke:var(--compare-a)"/&gt;
&lt;line x1="245" y1="180" x2="245" y2="225" stroke-width="1.5" stroke-dasharray="3,3" style="stroke:var(--border)"/&gt;
&lt;rect x="30" y="225" width="90" height="40" rx="4" stroke-width="1.5" style="fill:var(--compare-a-soft);stroke:var(--compare-a)"/&gt;
&lt;text x="75" y="245" font-size="12" text-anchor="middle" style="fill:var(--content)"&gt;v1 &amp;#8594; v2&lt;/text&gt;
&lt;text x="75" y="259" font-size="10" text-anchor="middle" style="fill:var(--secondary)"&gt;committed&lt;/text&gt;
&lt;rect x="200" y="225" width="90" height="40" rx="4" stroke-width="1.5" stroke-dasharray="3,3" style="fill:none;stroke:var(--border)"/&gt;
&lt;text x="245" y="245" font-size="12" text-anchor="middle" style="fill:var(--content)"&gt;v1 &amp;#8800; v2&lt;/text&gt;
&lt;text x="245" y="259" font-size="10" text-anchor="middle" style="fill:var(--secondary)"&gt;conflict, retry&lt;/text&gt;
&lt;text x="160" y="310" font-size="11" text-anchor="middle" style="fill:var(--secondary)"&gt;conflict caught at commit time&lt;/text&gt;
&lt;rect x="470" y="34" width="20" height="14" rx="2" stroke-width="1.5" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;
&lt;path d="M474,34 v-5 a6,6 0 0 1 12,0 v5" stroke-width="1.5" style="fill:none;stroke:var(--compare-b)"/&gt;
&lt;rect x="435" y="55" width="90" height="38" rx="4" stroke-width="1.5" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;
&lt;text x="480" y="79" font-size="13" text-anchor="middle" style="fill:var(--content)"&gt;Record&lt;/text&gt;
&lt;line x1="460" y1="93" x2="395" y2="140" stroke-width="1.5" style="stroke:var(--compare-b)"/&gt;
&lt;line x1="500" y1="93" x2="565" y2="140" stroke-width="1.5" stroke-dasharray="3,3" style="stroke:var(--border)"/&gt;
&lt;text x="545" y="120" font-size="10" text-anchor="middle" style="fill:var(--secondary)"&gt;blocked&lt;/text&gt;
&lt;rect x="350" y="140" width="90" height="40" rx="4" stroke-width="1.5" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;
&lt;text x="395" y="157" font-size="13" text-anchor="middle" style="fill:var(--content)"&gt;Txn A&lt;/text&gt;
&lt;text x="395" y="171" font-size="10" text-anchor="middle" style="fill:var(--secondary)"&gt;holds lock&lt;/text&gt;
&lt;rect x="520" y="140" width="90" height="40" rx="4" stroke-width="1.5" stroke-dasharray="3,3" style="fill:none;stroke:var(--border)"/&gt;
&lt;text x="565" y="157" font-size="13" text-anchor="middle" style="fill:var(--content)"&gt;Txn B&lt;/text&gt;
&lt;text x="565" y="171" font-size="10" text-anchor="middle" style="fill:var(--secondary)"&gt;waiting&lt;/text&gt;
&lt;line x1="395" y1="180" x2="395" y2="225" stroke-width="1.5" style="stroke:var(--compare-b)"/&gt;
&lt;line x1="565" y1="180" x2="565" y2="225" stroke-width="1.5" style="stroke:var(--compare-b)"/&gt;
&lt;rect x="350" y="225" width="90" height="40" rx="4" stroke-width="1.5" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;
&lt;text x="395" y="245" font-size="11" text-anchor="middle" style="fill:var(--content)"&gt;commits &amp;amp;&lt;/text&gt;
&lt;text x="395" y="259" font-size="10" text-anchor="middle" style="fill:var(--secondary)"&gt;releases lock&lt;/text&gt;
&lt;rect x="520" y="225" width="90" height="40" rx="4" stroke-width="1.5" style="fill:var(--compare-b-soft);stroke:var(--compare-b)"/&gt;
&lt;text x="565" y="245" font-size="11" text-anchor="middle" style="fill:var(--content)"&gt;acquires lock&lt;/text&gt;
&lt;text x="565" y="259" font-size="10" text-anchor="middle" style="fill:var(--secondary)"&gt;then runs&lt;/text&gt;
&lt;text x="480" y="310" font-size="11" text-anchor="middle" style="fill:var(--secondary)"&gt;conflict prevented up front&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Optimistic Locking&lt;/th&gt;
&lt;th&gt;Pessimistic Locking&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Access phase&lt;/td&gt;
&lt;td&gt;No lock taken; any transaction can read or begin writing the row immediately&lt;/td&gt;
&lt;td&gt;Lock acquired (e.g. SELECT FOR UPDATE) before the transaction reads or writes the row&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent access&lt;/td&gt;
&lt;td&gt;Other transactions freely read and write the same row in parallel&lt;/td&gt;
&lt;td&gt;Other transactions attempting the same row must wait for the lock holder to finish&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conflict detection timing&lt;/td&gt;
&lt;td&gt;Deferred until commit, via a version number, timestamp, or hash comparison&lt;/td&gt;
&lt;td&gt;Not needed as a separate step; the lock physically prevents overlapping access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On conflict&lt;/td&gt;
&lt;td&gt;Commit is rejected; the transaction is rolled back and typically retried&lt;/td&gt;
&lt;td&gt;No conflict occurs; the waiting transaction simply blocks until the lock is released&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implementation mechanism&lt;/td&gt;
&lt;td&gt;Application-level version column checked in the UPDATE&amp;rsquo;s WHERE clause&lt;/td&gt;
&lt;td&gt;Database-level row or table locks managed by the lock manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput under low contention&lt;/td&gt;
&lt;td&gt;High; no blocking overhead when collisions are rare&lt;/td&gt;
&lt;td&gt;Lower; locking overhead is paid even when no real conflict would occur&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavior under high contention&lt;/td&gt;
&lt;td&gt;Retry storms and wasted work as many transactions repeatedly fail and re-run&lt;/td&gt;
&lt;td&gt;Orderly queueing keeps correctness but serializes work and limits parallelism&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deadlock risk&lt;/td&gt;
&lt;td&gt;None, since no locks are ever held&lt;/td&gt;
&lt;td&gt;Possible when transactions acquire multiple locks in inconsistent order&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Optimistic locking detects conflicts at &lt;strong class="kw"&gt;commit time&lt;/strong&gt;; pessimistic locking prevents them via &lt;strong class="kw"&gt;upfront locking&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Optimistic locking never blocks other transactions, it only forces a &lt;strong class="kw"&gt;retry&lt;/strong&gt; on collision.&lt;/li&gt;
&lt;li&gt;Pessimistic locking holds an &lt;strong class="kw"&gt;exclusive lock&lt;/strong&gt; for the transaction&amp;rsquo;s full duration, serializing access to the row.&lt;/li&gt;
&lt;li&gt;Optimistic locking carries no &lt;strong class="kw"&gt;deadlock&lt;/strong&gt; risk because it never holds locks.&lt;/li&gt;
&lt;li&gt;Pessimistic locking trades raw throughput for &lt;strong class="kw"&gt;predictability&lt;/strong&gt; when contention is heavy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Optimistic Locking&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Serializable vs Weaker Isolation: Strict Ordering vs Controlled Anomalies</title><link>https://comparison.metacog.co.kr/posts/2026-09-06-serializable-vs-weaker-isolation-strict-ordering-vs-controll/</link><pubDate>Sun, 06 Sep 2026 09:47:51 +0900</pubDate><guid>https://comparison.metacog.co.kr/posts/2026-09-06-serializable-vs-weaker-isolation-strict-ordering-vs-controll/</guid><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;Serializable isolation guarantees that concurrent transactions produce a result equivalent to some &lt;strong class="kw"&gt;serial order&lt;/strong&gt;, eliminating every possible race condition. Weaker isolation levels like Read Committed or Snapshot trade that guarantee for higher throughput, deliberately allowing certain &lt;strong class="kw"&gt;read/write anomalies&lt;/strong&gt; that application code must tolerate or guard against.&lt;/p&gt;
&lt;h2 id="comparison-diagram"&gt;Comparison Diagram&lt;/h2&gt;
&lt;div class="compare-diagram"&gt;
&lt;svg viewBox="0 0 640 360" xmlns="http://www.w3.org/2000/svg"&gt;&lt;line x1="320" y1="20" x2="320" y2="340" style="stroke:var(--border)" stroke-width="1" stroke-dasharray="4 4"/&gt;&lt;text x="160" y="36" text-anchor="middle" style="fill:var(--primary)" font-size="18" font-weight="600"&gt;Serializable&lt;/text&gt;&lt;text x="480" y="36" text-anchor="middle" style="fill:var(--primary)" font-size="18" font-weight="600"&gt;Weaker Isolation&lt;/text&gt;&lt;line x1="40" y1="180" x2="280" y2="180" style="stroke:var(--border)" stroke-width="1"/&gt;&lt;line x1="360" y1="180" x2="600" y2="180" style="stroke:var(--border)" stroke-width="1"/&gt;&lt;rect x="45" y="110" width="100" height="36" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="95" y="133" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;T1&lt;/text&gt;&lt;rect x="175" y="110" width="100" height="36" rx="4" style="fill:var(--compare-a-soft);stroke:var(--compare-a)" stroke-width="1.5"/&gt;&lt;text x="225" y="133" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;T2&lt;/text&gt;&lt;path d="M40 200 L280 200" style="stroke:var(--compare-a)" stroke-width="2" marker-end="url(#arrowA)"/&gt;&lt;text x="160" y="225" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;time (T1 fully precedes T2)&lt;/text&gt;&lt;text x="160" y="260" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;No overlap in effect =&lt;/text&gt;&lt;text x="160" y="278" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;equivalent to a serial run&lt;/text&gt;&lt;rect x="45" y="100" width="140" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5" transform="translate(340,0)"/&gt;&lt;text x="455" y="123" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;T1&lt;/text&gt;&lt;rect x="115" y="144" width="140" height="36" rx="4" style="fill:var(--compare-b-soft);stroke:var(--compare-b)" stroke-width="1.5" transform="translate(340,0)"/&gt;&lt;text x="525" y="167" text-anchor="middle" style="fill:var(--content)" font-size="13"&gt;T2&lt;/text&gt;&lt;rect x="455" y="100" width="45" height="80" style="fill:none;stroke:var(--compare-b)" stroke-width="1" stroke-dasharray="3 3"/&gt;&lt;path d="M360 200 L600 200" style="stroke:var(--compare-b)" stroke-width="2" marker-end="url(#arrowB)"/&gt;&lt;text x="480" y="225" text-anchor="middle" style="fill:var(--secondary)" font-size="11"&gt;time (T1 and T2 overlap)&lt;/text&gt;&lt;text x="480" y="260" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;Overlap window =&lt;/text&gt;&lt;text x="480" y="278" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;dirty/non-repeatable reads,&lt;/text&gt;&lt;text x="480" y="296" text-anchor="middle" style="fill:var(--content)" font-size="12"&gt;phantoms, or write skew possible&lt;/text&gt;&lt;defs&gt;&lt;marker id="arrowA" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"&gt;&lt;path d="M0 0 L8 4 L0 8 z" style="fill:var(--compare-a)"/&gt;&lt;/marker&gt;&lt;marker id="arrowB" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"&gt;&lt;path d="M0 0 L8 4 L0 8 z" style="fill:var(--compare-b)"/&gt;&lt;/marker&gt;&lt;/defs&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;h2 id="comparison-table"&gt;Comparison Table&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Serializable&lt;/th&gt;
&lt;th&gt;Weaker Isolation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Isolation guarantee&lt;/td&gt;
&lt;td&gt;Result equivalent to some serial execution of all transactions&lt;/td&gt;
&lt;td&gt;Allows specific interleavings; guarantee varies by level (Read Committed, Repeatable Read, Snapshot)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency control&lt;/td&gt;
&lt;td&gt;Full conflict serializability via strict two-phase locking, serializable snapshot isolation (SSI), or predicate locks&lt;/td&gt;
&lt;td&gt;Row-level locks or MVCC snapshots that only block on narrower conflict sets (e.g. write-write)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anomalies prevented&lt;/td&gt;
&lt;td&gt;Dirty reads, non-repeatable reads, phantom reads, and write skew all eliminated&lt;/td&gt;
&lt;td&gt;Only a subset prevented; phantoms and write skew commonly remain possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contention behavior&lt;/td&gt;
&lt;td&gt;Higher rate of lock waits, deadlocks, or serialization-failure aborts under concurrent access&lt;/td&gt;
&lt;td&gt;Readers rarely block writers (MVCC) or lower lock scope, so contention is reduced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput impact&lt;/td&gt;
&lt;td&gt;Lower throughput and higher latency as concurrency increases, especially with hot rows&lt;/td&gt;
&lt;td&gt;Higher throughput and better scalability under contention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application responsibility&lt;/td&gt;
&lt;td&gt;Application can assume correctness; only needs to retry on serialization-failure errors&lt;/td&gt;
&lt;td&gt;Application must reason about anomalies and add explicit checks (e.g. version columns, SELECT FOR UPDATE)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conflict detection timing&lt;/td&gt;
&lt;td&gt;Detected either at lock-acquisition time or at commit time (optimistic serializable schemes)&lt;/td&gt;
&lt;td&gt;Detected only for the narrower conflicts the level covers, often just at commit for MVCC writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default in major databases&lt;/td&gt;
&lt;td&gt;Rarely the default; must be explicitly requested (e.g. SET TRANSACTION ISOLATION LEVEL SERIALIZABLE)&lt;/td&gt;
&lt;td&gt;Default in most systems out of the box (PostgreSQL/Oracle default to Read Committed, MySQL InnoDB to Repeatable Read)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="key-differences"&gt;Key Differences&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Serializable enforces true &lt;strong class="kw"&gt;serial equivalence&lt;/strong&gt;; weaker levels only rule out a defined subset of anomalies.&lt;/li&gt;
&lt;li&gt;Weaker isolation relies on &lt;strong class="kw"&gt;MVCC snapshots&lt;/strong&gt; or narrow locks, cutting contention compared to serializable&amp;rsquo;s broader locking or SSI conflict tracking.&lt;/li&gt;
&lt;li&gt;Under Serializable, correctness bugs shift into &lt;strong class="kw"&gt;retry loops&lt;/strong&gt; on abort; under weaker levels, they shift into missed application-level checks.&lt;/li&gt;
&lt;li&gt;Write skew is the classic anomaly Serializable closes that even &lt;strong class="kw"&gt;Snapshot Isolation&lt;/strong&gt; leaves open.&lt;/li&gt;
&lt;li&gt;Choosing a level is a runtime trade-off between guaranteed correctness and &lt;strong class="kw"&gt;throughput&lt;/strong&gt;, not a one-time schema decision.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-each"&gt;When to Use Each&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Serializable&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>