<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Lock on kastori</title><link>http://blog.kastori.dev/tags/lock/</link><description>Recent content in Lock on kastori</description><generator>Hugo -- gohugo.io</generator><language>ko-kr</language><lastBuildDate>Tue, 26 May 2026 00:00:00 +0900</lastBuildDate><atom:link href="http://blog.kastori.dev/tags/lock/index.xml" rel="self" type="application/rss+xml"/><item><title>[DB 완전 정복 #2] MySQL이 동시 요청을 처리하는 방법 — MVCC, 갭 락, 데드락</title><link>http://blog.kastori.dev/tech/2026-05-26-db-02-transaction-lock/</link><pubDate>Tue, 26 May 2026 00:00:00 +0900</pubDate><guid>http://blog.kastori.dev/tech/2026-05-26-db-02-transaction-lock/</guid><description>&lt;h2 id="동시에-100명이-같은-행을-읽고-있다면"&gt;&lt;a href="#%eb%8f%99%ec%8b%9c%ec%97%90-100%eb%aa%85%ec%9d%b4-%ea%b0%99%ec%9d%80-%ed%96%89%ec%9d%84-%ec%9d%bd%ea%b3%a0-%ec%9e%88%eb%8b%a4%eb%a9%b4" class="header-anchor"&gt;&lt;/a&gt;동시에 100명이 같은 행을 읽고 있다면
&lt;/h2&gt;&lt;p&gt;서버에 동시 요청이 들어오면 여러 트랜잭션이 같은 데이터를 동시에 읽고 쓴다. 이때 두 가지 문제가 생긴다.&lt;/p&gt;
&lt;p&gt;첫 번째는 &lt;strong&gt;일관성&lt;/strong&gt;. A가 읽는 도중 B가 데이터를 수정하면, A는 수정 전을 봐야 할까 수정 후를 봐야 할까?&lt;/p&gt;
&lt;p&gt;두 번째는 &lt;strong&gt;성능&lt;/strong&gt;. 읽기마다 락을 걸면 안전하지만, 100개 스레드가 모두 대기 상태가 된다.&lt;/p&gt;
&lt;p&gt;MySQL InnoDB는 &lt;strong&gt;MVCC&lt;/strong&gt;로 이 두 문제를 동시에 해결한다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mvcc--락-없이-일관된-읽기"&gt;&lt;a href="#mvcc--%eb%9d%bd-%ec%97%86%ec%9d%b4-%ec%9d%bc%ea%b4%80%eb%90%9c-%ec%9d%bd%ea%b8%b0" class="header-anchor"&gt;&lt;/a&gt;MVCC — 락 없이 일관된 읽기
&lt;/h2&gt;&lt;p&gt;MVCC(Multi-Version Concurrency Control)는 &lt;strong&gt;각 트랜잭션이 자신이 시작한 시점의 스냅샷을 본다&lt;/strong&gt;는 개념이다.&lt;/p&gt;
&lt;p&gt;InnoDB는 모든 행에 숨겨진 컬럼을 둔다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DB_TRX_ID : 이 행을 마지막으로 수정한 트랜잭션 ID
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DB_ROLL_PTR : Undo Log 포인터 (이전 버전으로 가는 링크)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;트랜잭션이 시작되면 &lt;strong&gt;스냅샷 ID&lt;/strong&gt;가 부여된다. 이후 데이터를 읽을 때, 해당 행의 &lt;code&gt;DB_TRX_ID&lt;/code&gt;가 자신의 스냅샷 ID보다 크면(= 내가 시작한 이후에 수정됨) Undo Log에서 이전 버전을 찾아 읽는다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[현재 데이터] TX_ID=200, name=&amp;#34;수정됨&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──▶ [Undo Log] TX_ID=100, name=&amp;#34;원본&amp;#34; ← TX 150이 읽는 버전
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──▶ [Undo Log] TX_ID=50, name=&amp;#34;초기값&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;트랜잭션 150은 TX 200이 수정한 내용을 보지 않는다. 락 없이도 일관된 데이터를 읽는다. 이것이 MVCC의 핵심이다.&lt;/p&gt;
&lt;p&gt;덕분에 &lt;strong&gt;읽기는 쓰기를 방해하지 않고, 쓰기는 읽기를 방해하지 않는다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="행-레벨-락--필요한-행만-잠근다"&gt;&lt;a href="#%ed%96%89-%eb%a0%88%eb%b2%a8-%eb%9d%bd--%ed%95%84%ec%9a%94%ed%95%9c-%ed%96%89%eb%a7%8c-%ec%9e%a0%ea%b7%bc%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;행 레벨 락 — 필요한 행만 잠근다
&lt;/h2&gt;&lt;p&gt;읽기는 MVCC로, 쓰기는 &lt;strong&gt;행 레벨 락&lt;/strong&gt;으로 처리한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;S락 (Shared Lock) : 읽기 락. 여러 트랜잭션이 동시에 획득 가능
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X락 (Exclusive Lock): 쓰기 락. 하나의 트랜잭션만 획득 가능
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;S + S → 허용
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;S + X → 대기
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X + X → 대기
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;InnoDB는 테이블 전체가 아닌 &lt;strong&gt;특정 행&lt;/strong&gt;에만 락을 건다. &lt;code&gt;id = 1&lt;/code&gt;에 락이 걸려 있어도 &lt;code&gt;id = 2&lt;/code&gt;는 다른 트랜잭션이 자유롭게 수정할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- id=1에만 X락
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- id=2, 3, 4 ... 다른 트랜잭션이 자유롭게 접근 가능
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="갭-락--phantom-read를-막는-방법"&gt;&lt;a href="#%ea%b0%ad-%eb%9d%bd--phantom-read%eb%a5%bc-%eb%a7%89%eb%8a%94-%eb%b0%a9%eb%b2%95" class="header-anchor"&gt;&lt;/a&gt;갭 락 — Phantom Read를 막는 방법
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;REPEATABLE READ&lt;/code&gt; 격리 수준에서는 같은 쿼리를 두 번 실행해도 결과가 같아야 한다. 하지만 락 없이 범위 조회를 하면, 그 사이 다른 트랜잭션이 새 행을 삽입(INSERT)할 수 있다. 이것이 &lt;strong&gt;Phantom Read&lt;/strong&gt;다.&lt;/p&gt;
&lt;p&gt;InnoDB는 이를 **갭 락(Gap Lock)**으로 막는다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- orders에 id: 10, 20, 30이 있다고 가정
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 10~30 범위의 &amp;#34;빈 공간(갭)&amp;#34;에도 락 → 이 범위에 INSERT 불가
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;갭 락은 인덱스 레코드 사이의 공간에 걸린다. 행이 없어도 &amp;ldquo;그 자리&amp;quot;를 잠가서 새 INSERT를 막는다.&lt;/p&gt;
&lt;p&gt;실무에서 갭 락 주의사항: 인덱스가 없는 컬럼으로 UPDATE하면 범위를 특정할 수 없어 테이블 전체에 갭 락이 걸린다. 다른 트랜잭션의 INSERT가 전부 막힌다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="데드락--서로를-기다리는-교착-상태"&gt;&lt;a href="#%eb%8d%b0%eb%93%9c%eb%9d%bd--%ec%84%9c%eb%a1%9c%eb%a5%bc-%ea%b8%b0%eb%8b%a4%eb%a6%ac%eb%8a%94-%ea%b5%90%ec%b0%a9-%ec%83%81%ed%83%9c" class="header-anchor"&gt;&lt;/a&gt;데드락 — 서로를 기다리는 교착 상태
&lt;/h2&gt;&lt;p&gt;두 트랜잭션이 서로 상대방의 락이 풀리기를 기다리면 데드락이 발생한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- TX A -- TX B
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- A가 id=1 X락 획득 -- B가 id=2 X락 획득
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- id=2 락 대기 (B가 가지고 있음) -- id=1 락 대기 (A가 가지고 있음)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- → 영원히 대기 = 데드락
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;InnoDB는 데드락을 자동으로 감지해서 &lt;strong&gt;한쪽 트랜잭션을 강제 롤백&lt;/strong&gt;한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR 1213 (40001): Deadlock found when trying to get lock;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;try restarting transaction
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="데드락-예방-4가지"&gt;&lt;a href="#%eb%8d%b0%eb%93%9c%eb%9d%bd-%ec%98%88%eb%b0%a9-4%ea%b0%80%ec%a7%80" class="header-anchor"&gt;&lt;/a&gt;데드락 예방 4가지
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 모든 트랜잭션에서 테이블/행 접근 순서를 일관되게 유지
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (항상 id 오름차순으로 락을 잡으면 교차가 발생하지 않는다)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 트랜잭션을 짧게 유지 — 락 보유 시간 최소화
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 인덱스를 통한 정확한 행 접근 — 갭 락 범위 최소화
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. @Transactional(timeout = 5) — 일정 시간 후 자동 롤백
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="낙관적-락-vs-비관적-락--언제-뭘-써야-하나"&gt;&lt;a href="#%eb%82%99%ea%b4%80%ec%a0%81-%eb%9d%bd-vs-%eb%b9%84%ea%b4%80%ec%a0%81-%eb%9d%bd--%ec%96%b8%ec%a0%9c-%eb%ad%98-%ec%8d%a8%ec%95%bc-%ed%95%98%eb%82%98" class="header-anchor"&gt;&lt;/a&gt;낙관적 락 vs 비관적 락 — 언제 뭘 써야 하나
&lt;/h2&gt;&lt;p&gt;충돌이 드문지 잦은지에 따라 전략이 달라진다.&lt;/p&gt;
&lt;h3 id="낙관적-락--충돌이-드물다"&gt;&lt;a href="#%eb%82%99%ea%b4%80%ec%a0%81-%eb%9d%bd--%ec%b6%a9%eb%8f%8c%ec%9d%b4-%eb%93%9c%eb%ac%bc%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;낙관적 락 — &amp;ldquo;충돌이 드물다&amp;rdquo;
&lt;/h3&gt;&lt;p&gt;읽을 때는 락을 걸지 않고, 수정할 때 version 조건을 추가한다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 읽기 (락 없음)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 결과: stock=10, version=5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 수정 (version 조건 추가)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 영향받은 행이 0이면 → 다른 트랜잭션이 먼저 수정 → 애플리케이션에서 재시도
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;JPA에서는 &lt;code&gt;@Version&lt;/code&gt; 어노테이션으로 자동 처리된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;적합한 케이스&lt;/strong&gt;: 게시글 수정, 사용자 프로필 변경 — 동시 수정이 드문 경우&lt;/p&gt;
&lt;h3 id="비관적-락--충돌이-잦다"&gt;&lt;a href="#%eb%b9%84%ea%b4%80%ec%a0%81-%eb%9d%bd--%ec%b6%a9%eb%8f%8c%ec%9d%b4-%ec%9e%a6%eb%8b%a4" class="header-anchor"&gt;&lt;/a&gt;비관적 락 — &amp;ldquo;충돌이 잦다&amp;rdquo;
&lt;/h3&gt;&lt;p&gt;읽을 때부터 X락을 걸어 다른 트랜잭션의 접근 자체를 막는다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- X락
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 커밋 시 락 해제
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;적합한 케이스&lt;/strong&gt;: 재고 차감, 선착순 쿠폰, 포인트 사용 — 동시 수정이 잦고 정확성이 중요한 경우&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="마치며"&gt;&lt;a href="#%eb%a7%88%ec%b9%98%eb%a9%b0" class="header-anchor"&gt;&lt;/a&gt;마치며
&lt;/h2&gt;&lt;p&gt;MVCC는 읽기와 쓰기가 서로를 방해하지 않게 해준다. 행 레벨 락은 필요한 부분만 잠근다. 갭 락은 Phantom Read를 막는다. 그리고 데드락은 락 순서를 통일하면 예방할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@Transactional&lt;/code&gt;을 쓸 때 격리 수준을 설정하는 이유, JPA의 &lt;code&gt;@Version&lt;/code&gt;이 하는 일 — 모두 여기서 나온다. 다음 편에서는 느린 쿼리를 진단하는 EXPLAIN을 다룬다.&lt;/p&gt;</description></item></channel></rss>