<?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/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>資料庫｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/%E8%B3%87%E6%96%99%E5%BA%AB/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>資料庫｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/%E8%B3%87%E6%96%99%E5%BA%AB/</link><width>144</width><height>144</height></image>
    <description>林鼎淵（Dean Lin）的部落格：生成式 AI 企業內訓講師、外商資安公司軟體專家、7 本書作者。400 多篇文章用白話分享生成式 AI 實戰、Claude Code 與 Vibe Coding 教學、團隊合作與職場成長。</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <copyright>© 2020–2026 林鼎淵 Dean Lin</copyright>
    <lastBuildDate>Mon, 04 Oct 2021 08:25:46 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/%E8%B3%87%E6%96%99%E5%BA%AB/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>[面試][資料庫]關聯式資料庫要如何設計避免超賣？</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-19/</link>
      <pubDate>Mon, 04 Oct 2021 08:25:46 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-19/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>網路商城特賣會常常會推出特定商品限量 1 組的搶購活動，在搶購過程除了會產生高併發的問題外，同時也需要考慮到「商品會不會超賣」，在現實生活中聽起來很不可思議；但在網路的世界裡，如果SQL邏輯沒有設計好，真的可能發生明明只限 1 組卻賣了 10 組商品的超賣事件。</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/cover_hu_e4b5e96a082dd443.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>庫存只剩 1 件，但卻有 10 個人買到？</p>
</blockquote>
<p>網路商城特賣會常常會推出<mark>特定商品限量 1 組的搶購活動</mark>，在搶購過程除了會產生高併發的問題外，同時也需要考慮到「<mark>商品會不會超賣</mark>」，在現實生活中聽起來很不可思議；但在網路的世界裡，如果SQL邏輯沒有設計好，真的可能發生明明只限 1 組卻賣了 10 組商品的超賣事件。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>關聯式資料庫要如何設計避免超賣？</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 什麼是悲觀鎖（Pessimistic Lock）？</li>
<li>2.2 什麼是樂觀鎖（Optimistic Lock）？</li>
<li>2.3 在 MySQL DB 實作悲觀鎖</li>
<li>2.4 在 MySQL DB 實作樂觀鎖</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 悲觀鎖、樂觀鎖優缺點</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-關聯式資料庫要如何設計避免超賣">1. 關聯式資料庫要如何設計避免超賣？<a class="h-anchor" href="#1-%e9%97%9c%e8%81%af%e5%bc%8f%e8%b3%87%e6%96%99%e5%ba%ab%e8%a6%81%e5%a6%82%e4%bd%95%e8%a8%ad%e8%a8%88%e9%81%bf%e5%85%8d%e8%b6%85%e8%b3%a3" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-面試官為什麼會問">1.1 面試官為什麼會問？<a class="h-anchor" href="#11-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<p>這算是資料庫的<mark>常見面試題</mark>，對<mark>金融、電商</mark>相關產業來說都快要變成必考題了。</p>
<p>通常問完這題後會緊接著詢問：「如果是用 noSQL 有什麼處理方案？」這部分會在<a href="https://deanlin.net/blog/202110/fullstack-career-notes-day-20/">明天的文章</a>跟大家講解。</p>
<hr>
<h3 class="h-nomark" id="12-面試官想從答案確認什麼">1.2 面試官想從答案確認什麼？<a class="h-anchor" href="#12-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>是否有資料庫 Transaction 的概念</li>
<li>了解悲觀鎖（Pessimistic Lock）、樂觀鎖（Optimistic Lock）的概念</li>
<li>知道悲觀鎖（Pessimistic Lock）、樂觀鎖（Optimistic Lock）優缺點、使用情境</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-筆者提供的簡答">1.3 筆者提供的簡答<a class="h-anchor" href="#13-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>我會依照<mark>不同的情境</mark>來做設計，如果<mark>寫入需求較高</mark>，我會選擇使用<code>悲觀鎖（Pessimistic Lock）</code>；因為它是直接使用資料庫 <code>Transaction</code> 的機制，保證了資料的穩定性。</p>
<p>如果<mark>讀取需求較高</mark>，我會選擇使用<code>樂觀鎖（Optimistic Lock）</code>；因為它可以承受較高的吞吐量，不過它鎖的特性會導致<mark>購買順序未必是先到先得</mark>，只是保證不會超賣。</p>
<hr>
<h2 class="h-nomark" id="2-回答問題所需具備的知識">2. 回答問題所需具備的知識<a class="h-anchor" href="#2-%e5%9b%9e%e7%ad%94%e5%95%8f%e9%a1%8c%e6%89%80%e9%9c%80%e5%85%b7%e5%82%99%e7%9a%84%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-什麼是悲觀鎖pessimistic-lock">2.1 什麼是悲觀鎖（Pessimistic Lock）？<a class="h-anchor" href="#21-%e4%bb%80%e9%ba%bc%e6%98%af%e6%82%b2%e8%a7%80%e9%8e%96pessimistic-lock" aria-label="這個段落的連結"></a></h3>
<p>就跟字面上的意思一樣，非常的悲觀；它認為 <mark>Table 裡面的 data 非常不安全，無時無刻都在變動</mark>，當一個 SQL command（可以理解為搶購的用戶）獲得悲觀鎖後，其他的 SQL command 無法對這個 data 進行修改，直到悲觀鎖被釋放後才能執行。</p>
<blockquote>
<p>資料表中用 Synchronized 實現的鎖均為悲觀鎖（ex：行鎖，表鎖，讀鎖，寫鎖）。</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="22-什麼是樂觀鎖optimistic-lock">2.2 什麼是樂觀鎖（Optimistic Lock）？<a class="h-anchor" href="#22-%e4%bb%80%e9%ba%bc%e6%98%af%e6%a8%82%e8%a7%80%e9%8e%96optimistic-lock" aria-label="這個段落的連結"></a></h3>
<p>相比悲觀鎖，它認為 <mark>Table 裡面的 data 變動頻率不會太頻繁</mark>，因此他會允許多個 SQL command 來操作 Table；但樂觀並不代表不負責，通常會在 Table 中增加一個 version 的欄位來做更新的確認。因此當 SQL command 想要變更欄位 data 時，會把<mark>之前取出的 version 跟現在的 version 做對比</mark>，如果相同就代表這段期間沒人修改可以執行；如果不同這次的操作就不會生效。</p>
<hr>
<h3 class="h-nomark" id="23-在-mysql-db-實作悲觀鎖">2.3 在 MySQL DB 實作悲觀鎖<a class="h-anchor" href="#23-%e5%9c%a8-mysql-db-%e5%af%a6%e4%bd%9c%e6%82%b2%e8%a7%80%e9%8e%96" aria-label="這個段落的連結"></a></h3>
<p><strong>情境描述：</strong></p>
<p>假設 A 跟 B 都想要買 iphone 手機，但商城的 iphone 庫存只剩下一隻，在不加鎖的狀況下同時下單會導致<mark>庫存變成負號</mark>；請問如何用<code>悲觀鎖</code>解決這個問題呢？</p>
<blockquote>
<p>只要安裝好 MySQL，跟著步驟操作即可模擬 A、B 客戶搶購。</p>
</blockquote>
<ul>
<li>
<p><strong>STEP 1</strong>：建立模擬資料</p>
<ul>
<li>
<p>先用 SQL command 建立一個簡單的 Table 並新增 data。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span><span style="color:#6e7681"> </span>AUTO_INCREMENT,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>VARCHAR(<span style="color:#a5d6ff">45</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">PRIMARY</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">KEY</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span>));<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">INSERT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">INTO</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span>)<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;iphone&#34;</span>,<span style="color:#a5d6ff">1</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>確認資料是否建立成功。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-01.webp" width="404" height="158" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 1）" fetchpriority="high" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p><strong>STEP 2</strong>：開一個視窗模擬 A 客戶搶購行為</p>
<ul>
<li>
<p>Transaction 的開始要用 <code>begin;</code>，不然 MySQL 會自動提交。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-02.webp" width="353" height="56" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 2）" loading="lazy" decoding="async"></p>
</li>
<li>
<p>在 A 購買前先將 <code>id=1</code> 的 data 加上悲觀鎖，<mark>此時這行 data 只有 A 可以操作</mark>。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">select</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72">from</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">for</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">update</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-03.webp" width="603" height="148" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 3）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p><strong>STEP 3</strong>：<mark>開另一個視窗</mark>模擬 B 客戶搶購行為</p>
<ul>
<li>
<p>一樣將 <code>id=1</code> 的 data 加上悲觀鎖，下完指令後便會<mark>進入等待的模式，要等 A 執行 commit 完成交易或是指令逾時</mark>才能繼續。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-04.webp" width="618" height="99" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 4）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p><strong>STEP 4</strong>：回到 A 客戶的視窗完成交易</p>
<ul>
<li>
<p>讓 A 執行購買的動作，將 data 的 num 減一。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">update</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">set</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>然後檢視修改後的 data 是否符合預期。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">select</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72">from</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>最後下 <code>commit;</code> 結束這次交易。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-05.webp" width="585" height="281" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 5）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li>
<p><strong>STEP 5</strong>：當 A 完成交易後回到 B 的視窗<br>你會發現因為 A 釋放了悲觀鎖，所以 B 結束等待並且獲得鎖；但 data 的 num 已經變成 0 了，只能放棄購買。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-06.webp" width="585" height="143" alt="2.3 在 MySQL DB 實作悲觀鎖（圖 6）" loading="lazy" decoding="async"></p>
<blockquote>
<p>7.89 秒是等待獲得鎖的時間。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="24-在-mysql-db-實作樂觀鎖">2.4 在 MySQL DB 實作樂觀鎖<a class="h-anchor" href="#24-%e5%9c%a8-mysql-db-%e5%af%a6%e4%bd%9c%e6%a8%82%e8%a7%80%e9%8e%96" aria-label="這個段落的連結"></a></h3>
<p><strong>情境描述：</strong></p>
<p>假設 A 跟 B 都想要買 iphone 手機，但商城的 iphone 庫存只剩下一隻，在不加鎖的狀況下同時下單會導致<mark>庫存變成負號</mark>；請問如何用<code>樂觀鎖</code>解決這個問題呢？</p>
<blockquote>
<p>觀察兩者實作上的邏輯差異。</p>
</blockquote>
<ul>
<li><strong>STEP 1</strong>：建立模擬資料
<ul>
<li>
<p>先用 SQL command 建立一個簡單的 Table 並插入 data，<mark>比起悲觀鎖的 Table，我們多了一個 version 的欄位</mark>。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span><span style="color:#6e7681"> </span>AUTO_INCREMENT,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>VARCHAR(<span style="color:#a5d6ff">45</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#ff7b72">version</span><span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">PRIMARY</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">KEY</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span>));<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">INSERT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">INTO</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#ff7b72">version</span><span style="color:#ff7b72;font-weight:bold">`</span>)<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;iphone&#34;</span>,<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">0</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>確認資料是否建立成功。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-07.webp" width="427" height="143" alt="2.4 在 MySQL DB 實作樂觀鎖（圖 7）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li><strong>STEP 2</strong>：規劃實作邏輯思路
<ul>
<li>A 跟 B 客戶在購買前會先將 <code>id=1</code> 的 data 找出來。</li>
<li>A 先購買，此時會以 <code>id=1 和 version=0</code> 作為 where 條件來更新 data，更新後 num -1、version +1 變成 <code>id=1、num=0、version=1</code>。</li>
<li>B 購買時會因為同樣是用 <code>id=1 和 version=0</code> 為 where 條件，所以會因為找不到 data 而無法更新。</li>
</ul>
</li>
<li><strong>STEP 3</strong>：模擬搶購情境，A 跟 B 客戶購買前先查詢庫存
<ul>
<li>
<p>A 及 B 執行查詢的 SQL command 會取得相同的 iphone 庫存資訊。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span>num,<span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-08.webp" width="621" height="143" alt="2.4 在 MySQL DB 實作樂觀鎖（圖 8）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li><strong>STEP 4</strong>：A 先執行購買的 SQL command
<ul>
<li>
<p>A 先購買 iphone 並更新 data。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">update</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">set</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span>num<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span>,<span style="color:#6e7681"> </span><span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">and</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">0</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p>檢視修改後的 data 是否符合預期。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">SELECT</span><span style="color:#6e7681"> </span>num,<span style="color:#ff7b72">version</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>id<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">1</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-09.webp" width="978" height="370" alt="2.4 在 MySQL DB 實作樂觀鎖（圖 9）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
<li><strong>STEP 5</strong>：B 再執行購買的 SQL command
<ul>
<li>
<p>此時 B 的購買就會失敗，在執行購買的 SQL command 後你會發現沒有 Row 被更新，因為<mark>庫存資訊已更新，但 SQL command 還是舊的</mark>。</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-10.webp" width="978" height="75" alt="2.4 在 MySQL DB 實作樂觀鎖（圖 10）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
</ul>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. 衍伸問題<a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-悲觀鎖樂觀鎖優缺點">3.1 悲觀鎖、樂觀鎖優缺點<a class="h-anchor" href="#31-%e6%82%b2%e8%a7%80%e9%8e%96%e6%a8%82%e8%a7%80%e9%8e%96%e5%84%aa%e7%bc%ba%e9%bb%9e" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：對於關聯式資料庫「鎖」的認知程度</mark></p>
<ul>
<li><strong>悲觀鎖（Pessimistic Lock）</strong>
<ul>
<li><strong>優點</strong><br>使用資料庫 <code>Transaction</code> 的機制來強迫執行的順序。</li>
<li><strong>缺點</strong><br>一但加入 Transaction 的機制，會導致其他的 SQL command 針對這個 data <mark>除了查詢功能外全部卡死</mark>，如果這段 Transaction 執行時間較長，<mark>會給使用者不良的體驗並造成系統吞吐量下降</mark>。</li>
</ul>
</li>
<li><strong>樂觀鎖（Optimistic Lock）</strong>
<ul>
<li><strong>優點</strong><br>因為沒有在資料庫加鎖，所以 SQL command 都會對 data 進行操作，<mark>只有在更新 data 時才會做驗證</mark>；這樣做就能<mark>避免因使用悲觀鎖而導致吞吐量下降</mark>的問題。</li>
<li><strong>缺點</strong><br>因為樂觀鎖是我們人為實現的，所以如果<mark>換一個業務場景可能會不適用</mark>，甚至可能因為其他的 SQL command 導致錯誤。</li>
</ul>
</li>
</ul>
<hr>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li><a href="https://medium.com/dean-lin/2cabb858726d#55e3">理解資料庫『悲觀鎖』和『樂觀鎖』的觀念(筆者部落格)</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-19/images/fullstack-career-notes-day-19-11.webp" width="1024" height="233" alt="參考資源（圖 11）" loading="lazy" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>[面試][資料庫]設計資料庫時會考量哪些點？</title>
      <link>https://deanlin.net/blog/202110/fullstack-career-notes-day-17/</link>
      <pubDate>Sat, 02 Oct 2021 08:46:41 +0800</pubDate>
      <guid>https://deanlin.net/blog/202110/fullstack-career-notes-day-17/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>職涯成長</category>
      <description>如果公司有專門的 DBA，後端工程師就可以把心力放在資料邏輯的處理上；但筆者詢問周圍的朋友，似乎有不少公司都是讓後端工程師兼任 DBA...</description>
      <media:content url="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/cover_hu_69932cbb52ce4eda.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<blockquote>
<p>好的設計讓你上天堂，爛的設計讓你下地獄。</p>
</blockquote>
<p>如果公司有專門的 DBA，後端工程師就可以把心力放在資料邏輯的處理上；但筆者詢問周圍的朋友，似乎有不少公司都是讓後端工程師兼任 DBA&hellip;</p>
<p>筆者在這塊也並非專家，主要是分享自己實作中會注意的基礎細節，<mark>避免犯下一些原則性錯誤</mark>。</p>
<h2 id="大綱">大綱<a class="h-anchor" href="#%e5%a4%a7%e7%b6%b1" aria-label="這個段落的連結"></a></h2>
<ol>
<li><strong>設計資料庫時會考量哪些點？</strong>
<ul>
<li>1.1 面試官為什麼會問？</li>
<li>1.2 面試官想從答案確認什麼？</li>
<li>1.3 筆者提供的簡答</li>
</ul>
</li>
<li><strong>回答問題所需具備的知識</strong>
<ul>
<li>2.1 在 Table 建立有效的 Index</li>
<li>2.2 資料庫設計與優化</li>
<li>2.3 MySQL 與 PostgreSQL 的區別及應用場景</li>
</ul>
</li>
<li><strong>衍伸問題</strong>
<ul>
<li>3.1 為什麼 Index 不能建立太多？</li>
<li>3.2 寫 DB SQL 時會注意什麼？</li>
<li>3.3 有用過 NoSQL 嗎？什麼應用情境下會使用呢？</li>
<li>3.4 你會用哪些點來評估要使用關聯式資料庫 or 非關聯式資料庫？</li>
</ul>
</li>
</ol>
<hr>
<h2 class="h-nomark" id="1-設計資料庫時會考量哪些點">1. 設計資料庫時會考量哪些點？<a class="h-anchor" href="#1-%e8%a8%ad%e8%a8%88%e8%b3%87%e6%96%99%e5%ba%ab%e6%99%82%e6%9c%83%e8%80%83%e9%87%8f%e5%93%aa%e4%ba%9b%e9%bb%9e" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="11-面試官為什麼會問">1.1 面試官為什麼會問？<a class="h-anchor" href="#11-%e9%9d%a2%e8%a9%a6%e5%ae%98%e7%82%ba%e4%bb%80%e9%ba%bc%e6%9c%83%e5%95%8f" aria-label="這個段落的連結"></a></h3>
<ul>
<li>因為<mark>履歷資訊</mark>中表明擅長多種資料庫（Database）
<ul>
<li>MySQL、MSSQL、PostgreSQL</li>
</ul>
</li>
<li>且<mark>履歷資訊</mark>中還表明有優化資料庫的經驗。
<ul>
<li><strong>XXX 資料庫系統</strong><br>將 Visual Basic 的單機版程式改寫成網頁系統，整合過去冗贅資料表，優化使用者體驗。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="12-面試官想從答案確認什麼">1.2 面試官想從答案確認什麼？<a class="h-anchor" href="#12-%e9%9d%a2%e8%a9%a6%e5%ae%98%e6%83%b3%e5%be%9e%e7%ad%94%e6%a1%88%e7%a2%ba%e8%aa%8d%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<ul>
<li>選用資料庫前，會思考哪些問題</li>
<li>設計資料庫時會注意哪些細節</li>
<li>同樣都是關聯式資料庫，你知道他們間的差異嗎？</li>
<li>有用過哪些方式優化資料庫效能</li>
<li>你對 NoSQL 的認知有多少</li>
</ul>
<hr>
<h3 class="h-nomark" id="13-筆者提供的簡答">1.3 筆者提供的簡答<a class="h-anchor" href="#13-%e7%ad%86%e8%80%85%e6%8f%90%e4%be%9b%e7%9a%84%e7%b0%a1%e7%ad%94" aria-label="這個段落的連結"></a></h3>
<p>在決定要使用什麼資料庫前，我會先分析專案的<mark>應用場景</mark>：</p>
<ul>
<li>像是<code>商城</code>會有金流，所以資料穩定性與 Transaction 機制是很重要的。</li>
<li>如果要設計<mark>地圖平台</mark>，就會選擇對 GIS 支援度高的資料庫。</li>
</ul>
<p>在設計資料庫時會畫 <code>ER Model</code> 來分析資料間的關聯與屬性；在優化方面，為了要提高搜尋效率會在 Table <code>建立 Index</code>，同時也會依據實際業務設計<mark>欄位適合的長度及 Type</mark>。</p>
<hr>
<h2 class="h-nomark" id="2-回答問題所需具備的知識">2. 回答問題所需具備的知識<a class="h-anchor" href="#2-%e5%9b%9e%e7%ad%94%e5%95%8f%e9%a1%8c%e6%89%80%e9%9c%80%e5%85%b7%e5%82%99%e7%9a%84%e7%9f%a5%e8%ad%98" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="21-在-table-建立有效的-index">2.1 在 Table 建立有效的 Index<a class="h-anchor" href="#21-%e5%9c%a8-table-%e5%bb%ba%e7%ab%8b%e6%9c%89%e6%95%88%e7%9a%84-index" aria-label="這個段落的連結"></a></h3>
<p>我們知道在 Table 建立 Index 可以增加搜尋效率，但你知道自己建立的 Index 有沒有派上用場嗎？筆者將在這個小節透過實作，帶大家了解 <code>Coverage Index (覆蓋索引)</code>的意義。</p>
<ul>
<li>
<p><strong>SETP 1</strong>：建立測試用的 <strong>tmp_user</strong> Table</p>
<ul>
<li><strong>id</strong> 為 primary key</li>
<li><strong>name</strong> 為 index</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">create</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">table</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>id<span style="color:#6e7681"> </span>bigint(<span style="color:#a5d6ff">20</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">not</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">null</span><span style="color:#6e7681"> </span>auto_increment<span style="color:#6e7681"> </span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>name<span style="color:#6e7681"> </span>varchar(<span style="color:#a5d6ff">255</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">not</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">null</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>password<span style="color:#6e7681"> </span>varchar(<span style="color:#a5d6ff">255</span>)<span style="color:#6e7681"> </span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#ff7b72">index</span><span style="color:#6e7681"> </span>index_name<span style="color:#6e7681"> </span>(name),<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#ff7b72">primary</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">key</span><span style="color:#6e7681"> </span>(id))<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span>engine<span style="color:#ff7b72;font-weight:bold">=</span>innodb<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#ff7b72">default</span><span style="color:#6e7681"> </span>character<span style="color:#6e7681"> </span><span style="color:#ff7b72">set</span><span style="color:#ff7b72;font-weight:bold">=</span>utf8<span style="color:#6e7681"> </span><span style="color:#ff7b72">collate</span><span style="color:#ff7b72;font-weight:bold">=</span>utf8_general_ci;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">insert</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">into</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span>(name,<span style="color:#6e7681"> </span>password)<span style="color:#6e7681"> </span><span style="color:#ff7b72">values</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;寶寶不說&#34;</span>,<span style="color:#a5d6ff">&#34;123&#34;</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p><strong>SETP 2</strong>：下 SQL 指令了解是否使用 Index</p>
<blockquote>
<p>觀察最後「Extra」欄位的資訊有否有 <code>Using index</code></p>
</blockquote>
<ul>
<li>
<p><strong>SQL A</strong>：<code>explain select id from user_table where name= '寶寶不說';</code></p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/fullstack-career-notes-day-17-01.webp" width="1024" height="118" alt="2.1 在 Table 建立有效的 Index（圖 1）" fetchpriority="high" decoding="async"></p>
<p>因為 <code>name</code> 有建立 index，所以在查詢到 <code>name</code> 後可以直接與 <code>id</code> 對應返回結果；因為 name 已經覆蓋了查詢資料的需求，所以稱為 「Coverage Index」。</p>
</li>
<li>
<p><strong>SQL B</strong>：<code>explain select password from user_table where name= '寶寶不說';</code></p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/fullstack-career-notes-day-17-02.webp" width="1024" height="118" alt="2.1 在 Table 建立有效的 Index（圖 2）" loading="lazy" decoding="async"></p>
<p>透過 <code>name</code> 找到「寶寶不說」對應的 <code>id</code>，但接著要透過 id 這個 <code>primary key</code>，在 Table 重新輪詢才能取得這一列的完整資料。</p>
</li>
</ul>
</li>
<li>
<p><strong>SETP 3</strong>：建立 Coverage Index 優化 SQL B</p>
<ul>
<li>
<p><strong>先將資料庫 drop 掉</strong>：<code>drop table tmp_user;</code></p>
</li>
<li>
<p><strong>重新建立測試資料以及「Coverage Index」</strong><br>為了避免用 primary key 再次從 Table 查詢的耗能，我們可以<code>把 select 與 where 用到的欄位(name、password)做聯合索引</code>，這樣就能夠直接使用索引查詢，而不需用 primary key 重新比對，這就是「Coverage Index」的應用。</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">create</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">table</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>id<span style="color:#6e7681"> </span>bigint(<span style="color:#a5d6ff">20</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">not</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">null</span><span style="color:#6e7681"> </span>auto_increment<span style="color:#6e7681"> </span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>name<span style="color:#6e7681"> </span>varchar(<span style="color:#a5d6ff">255</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">not</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">null</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>password<span style="color:#6e7681"> </span>varchar(<span style="color:#a5d6ff">255</span>)<span style="color:#6e7681"> </span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72">primary</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">key</span><span style="color:#6e7681"> </span>(id))<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>engine<span style="color:#ff7b72;font-weight:bold">=</span>innodb<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72">default</span><span style="color:#6e7681"> </span>character<span style="color:#6e7681"> </span><span style="color:#ff7b72">set</span><span style="color:#ff7b72;font-weight:bold">=</span>utf8<span style="color:#6e7681"> </span><span style="color:#ff7b72">collate</span><span style="color:#ff7b72;font-weight:bold">=</span>utf8_general_ci;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">insert</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">into</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span>(name,<span style="color:#6e7681"> </span>password)<span style="color:#6e7681"> </span><span style="color:#ff7b72">values</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;寶寶不說&#34;</span>,<span style="color:#a5d6ff">&#34;123&#34;</span>);<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">alter</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">table</span><span style="color:#6e7681"> </span>tmp_user<span style="color:#6e7681"> </span><span style="color:#ff7b72">add</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">index</span><span style="color:#6e7681"> </span>name_password(name,password);<span style="color:#6e7681">
</span></span></span></code></pre></div></li>
<li>
<p><strong>再次執行 SQL B</strong>：<code>explain select password from tmp_user where name = '寶寶不說';</code><br>本次的查詢就可以在「Extra」欄位看到 <code>Using index</code> 嚕～</p>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/fullstack-career-notes-day-17-03.webp" width="1024" height="116" alt="2.1 在 Table 建立有效的 Index（圖 3）" loading="lazy" decoding="async"></p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="22-資料庫設計與優化">2.2 資料庫設計與優化<a class="h-anchor" href="#22-%e8%b3%87%e6%96%99%e5%ba%ab%e8%a8%ad%e8%a8%88%e8%88%87%e5%84%aa%e5%8c%96" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>使用 ER Model 做規畫</strong><br>在需求規格出來後，我們可以先透過 ER Model 整理資料間的關聯性，然後以此為基礎設計資料庫；向新人說明 Table 間的關聯時，它也是一個非常棒的輔助工具。</li>
<li><strong>設計合適的文字欄位</strong>
<ul>
<li>如果不確定該欄位會填入的文字長度，會選擇 VARCHAR 這種依據文字長度來使用儲存空間的 Type。</li>
<li>以時常變更文字的欄位來說，會選擇 CHAR 而非 VARCHAR；因為 VARCHAR 的欄位在儲存時會需要額外的計算。</li>
<li>盡量<code>避免使用 TEXT/BLOB</code> 這類的 Type，它們在查詢時會消耗更多的效能。</li>
<li>根據實際業務需求設計欄位長度，</li>
</ul>
</li>
<li><strong>減少 SQL 寫入次數</strong><br>以 Insert 來做舉例，假設有 1000 筆資料要寫入 Table；一次插入 1000 筆的執行時間會遠遠小於分 1000 次插入的時間。</li>
<li><strong>適當的增加冗餘欄位</strong><br>在設計資料庫時要盡量符合三大正規化；但 <mark>Table 間的關聯越複雜也會導致查詢效率的下降</mark>，因此有時會適當的增加冗餘欄位，用犧牲磁碟空間的方式換取更高的查詢效率。</li>
<li><strong>定期審視資料庫的規劃</strong><br>如果沒有專門的 DBA，那資料庫通常會有以下問題需要修正：
<ul>
<li>許多欄位因為不確定實際業務需求，直接把<mark>欄位長度開到最大</mark>。</li>
<li>因為新需求不斷產生，在思慮不夠周密的狀況下容易<mark>新增許多無用欄位</mark>。</li>
<li>在系統版本的更迭中，<mark>沒有整理</mark>不符合實際業務需求的<code>檢視表（View）</code>。</li>
<li>後端存取 Table 的欄位改變，但 Table 的 Index 並沒有一起調整，導致<mark>許多 Index 並沒有發揮作用</mark>。</li>
</ul>
</li>
</ul>
<hr>
<h3 class="h-nomark" id="23-mysql-與-postgresql-的區別及應用場景">2.3 MySQL 與 PostgreSQL 的區別及應用場景<a class="h-anchor" href="#23-mysql-%e8%88%87-postgresql-%e7%9a%84%e5%8d%80%e5%88%a5%e5%8f%8a%e6%87%89%e7%94%a8%e5%a0%b4%e6%99%af" aria-label="這個段落的連結"></a></h3>
<p><code>MySQL</code> 是世界上最流行的資料庫之一，世界上有非常多的 Web Application 都採用它作為解決方案；但在被收購後分出了非常多的版本，<mark>有免費也有收費的</mark>；而 <code>PostgreSQL</code> 基於 BSD/MIT 的 linecense，為<mark>完全開源的資料庫</mark>。</p>
<ul>
<li><strong>PostgreSQL 的優勢</strong>
<ul>
<li><strong>不僅是關聯式資料庫，還支援 jsonb 的格式</strong><br>相比於 json，jsonb 以二進位格式儲存且可以使用索引（Index），你可以利用它<mark>整合關聯式資料與非關聯式資料</mark>。</li>
<li><strong>有 PostGIS 讓他成為很棒的空間資料庫</strong><br>相比於 MySQL spatial extension，PostGIS 可以<mark>支持「二維、三維、曲線」的空間類型</mark>，並有豐富的空間操作函數。</li>
<li><strong>可以輕鬆與外部資料庫關聯</strong><br>透過 FDW（Foreign Data Wrapper），你可以把外部資料庫（ex：MySQL、Oracle、CSV）當成自己資料庫中的 Table 查詢，讓 PostgreSQL 成為<mark>聯合資料庫</mark>。</li>
</ul>
</li>
<li><strong>MySQL 的優勢</strong>
<ul>
<li><strong>會 MySQL 的工程師更多</strong><br><mark>在招募工程師時更容易</mark>，遇到問題時也有豐富的社群資源提供解答。</li>
<li><strong>MySQL 使用 Thread 而 PostgreSQL 使用 Process</strong>
<ul>
<li>
<p>Process 比 Thread 要花更多時間建立連線。</p>
</li>
<li>
<p>Process 因為缺失 Thread 實作的「共享」特性，使得每次連線建立時，都需要耗費較高的記憶體空間配置。</p>
<blockquote>
<p>PostgreSQL 可以搭配 Connection Pool 的外掛解決這個問題，但架構上就會變更複雜。</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><strong>應用場景</strong>
<ul>
<li><strong>MySQL</strong><br>適合業務邏輯相對簡單、較少處理資料一致化問題的網際網路場景。</li>
<li><strong>PostgreSQL</strong><br>專案資料寫入頻繁，且資料庫龐大而複雜，內容需要高度一致性，有 GIS 需求。</li>
</ul>
</li>
</ul>
<blockquote>
<p>其實在 DB 的選擇上，除了考慮專案適用的場景外，還要考量到<mark>團隊內的工程師對哪個比較擅長；思考萬一發生意外時有誰可以救場</mark>，不然災難復原是一場惡夢。</p>
</blockquote>
<hr>
<h2 class="h-nomark" id="3-衍伸問題">3. <strong>衍伸問題</strong><a class="h-anchor" href="#3-%e8%a1%8d%e4%bc%b8%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="31-為什麼-index-不能建立太多">3.1 為什麼 Index 不能建立太多？<a class="h-anchor" href="#31-%e7%82%ba%e4%bb%80%e9%ba%bc-index-%e4%b8%8d%e8%83%bd%e5%bb%ba%e7%ab%8b%e5%a4%aa%e5%a4%9a" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：了解 Index 對資料庫效能的影響</mark></p>
<p>Index 的數量太多，會影嚮 DML（Insert、Update、Delete）的效能，<mark>因為 Table 資料更新時也要連帶更新 Index</mark>。</p>
<hr>
<h3 class="h-nomark" id="32-寫-db-sql-時會注意什麼">3.2 寫 DB SQL 時會注意什麼？<a class="h-anchor" href="#32-%e5%af%ab-db-sql-%e6%99%82%e6%9c%83%e6%b3%a8%e6%84%8f%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認求職者的 SQL 基本功</mark></p>
<ul>
<li>不要回傳大量資料，要有條件的<mark>回傳固定筆數</mark>（ex：limit）。</li>
<li>Select 時盡量不要使用「*」，<mark>只抓取需要的欄位</mark>。</li>
<li>Select 充分使用 <code>Index</code> 來做搜尋。</li>
<li><mark>避免使用不兼容的型態</mark>做比較，像是 FLOAT 跟 INT、CHAR 跟 VARCHAR。</li>
</ul>
<hr>
<h3 class="h-nomark" id="33-有用過-nosql-嗎什麼應用情境下會使用呢">3.3 有用過 NoSQL 嗎？什麼應用情境下會使用呢？<a class="h-anchor" href="#33-%e6%9c%89%e7%94%a8%e9%81%8e-nosql-%e5%97%8e%e4%bb%80%e9%ba%bc%e6%87%89%e7%94%a8%e6%83%85%e5%a2%83%e4%b8%8b%e6%9c%83%e4%bd%bf%e7%94%a8%e5%91%a2" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認求職者對 NoSQL 的認知</mark></p>
<blockquote>
<p>即便你的履歷中沒有寫自己會 NoSQL，還是會遇到這類考題，所以建議至少有基礎的認知。</p>
</blockquote>
<ul>
<li><strong>資料表欄位常常變化</strong><br>像是商城這種擁有龐大資料量的系統，在關連式資料庫中如果要 Update 欄位有很多顧慮；因為每個操作都需要重頭到尾輪詢一次 Table，非常消耗時間。<br>如果使用 <code>MongoDB</code> 這類的 NoSQL，就可以很靈活的對欄位進行調整。</li>
<li><strong>需要高速的讀寫</strong><br>像是 <code>Redis</code> 這類記憶體資料庫，單一節點就能應付每秒 10 萬次的讀寫請求。</li>
<li><strong>資料量龐大，有擴充需求</strong><br>NoSQL 具備<mark>水平擴充</mark>能力，只要增加新的伺服器節點就能擴充資料庫容量；且對於伺服器的性能要求較低，可使用<code>較便宜</code>的電腦進行擴充。</li>
</ul>
<hr>
<h3 class="h-nomark" id="34-你會用哪些點來評估要使用關聯式資料庫-or-非關聯式資料庫">3.4 你會用哪些點來評估要使用關聯式資料庫 or 非關聯式資料庫？<a class="h-anchor" href="#34-%e4%bd%a0%e6%9c%83%e7%94%a8%e5%93%aa%e4%ba%9b%e9%bb%9e%e4%be%86%e8%a9%95%e4%bc%b0%e8%a6%81%e4%bd%bf%e7%94%a8%e9%97%9c%e8%81%af%e5%bc%8f%e8%b3%87%e6%96%99%e5%ba%ab-or-%e9%9d%9e%e9%97%9c%e8%81%af%e5%bc%8f%e8%b3%87%e6%96%99%e5%ba%ab" aria-label="這個段落的連結"></a></h3>
<p><mark>考點：確認求職者是否有評估的能力與經驗</mark></p>
<p>我會考慮的點有<mark>資料量多寡、併發量、實時性、資料一致性、未來擴充性、資料穩定性、維護成本</mark>。</p>
<p>以企業內部管理系統來說，因為資料量少、併發數少、未來擴充需求較低，所以會選擇關連式資料庫；如果是遊戲的排行榜頁面，因為資料量大、併發數高、且要求高的更新頻率，所以我會考慮使用記憶體資料庫（ex：Redis）。</p>
<hr>
<p>感謝大家的閱讀，如果喜歡我的文章可以<code>訂閱</code>接收通知；如果有幫助到你，按<code>Like</code>可以讓我更有寫文的動力，我們明天見～</p>
<h3 id="參考資源">參考資源<a class="h-anchor" href="#%e5%8f%83%e8%80%83%e8%b3%87%e6%ba%90" aria-label="這個段落的連結"></a></h3>
<ol>
<li>MySQL 資料庫面試題</li>
<li><a href="https://medium.com/dean-lin/f852af308c27">你在 Table 建立的 Index 真的有效嗎？用範例帶你理解 Coverage Index 的意義(筆者部落格)</a></li>
<li><a href="https://zi.media/@yidianzixun/post/FaABsS">MySQL 與 PostgreSQL 相比哪個更好？</a></li>
</ol>
<hr>
<hr>
<blockquote>
<p><img src="https://deanlin.net/blog/202110/fullstack-career-notes-day-17/images/fullstack-career-notes-day-17-04.webp" width="1024" height="233" alt="參考資源（圖 4）" loading="lazy" decoding="async"></p>
<p>在許多人的幫助下，本系列文章已成功出版，除了添加新的篇章，更完善了每個案例的應對進退；如果對現在的職涯感到迷茫，也許這本書能帶給你不一樣的觀點～</p>
<p><strong>天瓏書局：</strong> <a href="https://www.tenlong.com.tw/products/9786263334571">https://www.tenlong.com.tw/products/9786263334571</a></p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>理解資料庫『悲觀鎖』和『樂觀鎖』的觀念</title>
      <link>https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/</link>
      <pubDate>Fri, 12 Mar 2021 18:15:06 +0800</pubDate>
      <guid>https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>沒有經過實際操作的理論都只是空談，這篇文章我會附上自己的MySQL 語法 方便大家在自己的環境中驗證這個資料庫的觀念，讓日後處理併發問題時有更完整的思路。</description>
      <media:content url="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-og_hu_8e7dc770a70e0cb.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>沒有經過實際操作的理論都只是空談，這篇文章我會附上自己的MySQL 語法方便大家在自己的環境中驗證這個資料庫的觀念，讓日後處理併發問題時有更完整的思路。</p>
<pre tabindex="0"><code>一、為什麼要了解他們 🔐？
二、從字面上了解悲觀鎖、樂觀鎖
三、在 MySQL DB 實作悲觀鎖
四、在 MySQL DB 實作樂觀鎖
五、悲觀鎖、樂觀鎖優缺點
</code></pre><p><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-01.webp" width="1400" height="787" alt="理解資料庫『悲觀鎖』和『樂觀鎖』的觀念（圖 1）" fetchpriority="high" decoding="async"></p>
<h2 id="一為什麼要了解他們-">一、為什麼要了解他們 🔐？<a class="h-anchor" href="#%e4%b8%80%e7%82%ba%e4%bb%80%e9%ba%bc%e8%a6%81%e4%ba%86%e8%a7%a3%e4%bb%96%e5%80%91-" aria-label="這個段落的連結"></a></h2>
<p>網路商城開特賣會的時候常常會推出一件商品只限 10 人搶購，在搶購的過程除了會產生高併發的問題外，同時也需要考慮到『商品會不會超賣』，在現實生活中聽起來很像不可思議，但在網路的世界裡如果沒做好資料庫的防護真的可能發生明明只限 10 人搶購卻有 30 個人搶到商品的超賣事件。</p>
<h3 class="h-nomark" id="-今天想要解決的問題">❓ 今天想要解決的問題：<a class="h-anchor" href="#-%e4%bb%8a%e5%a4%a9%e6%83%b3%e8%a6%81%e8%a7%a3%e6%b1%ba%e7%9a%84%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p>假設 A 跟 B 都想要買 iphone 手機，但商城的 iphone 庫存只剩下一隻，在不加鎖的狀況下同時下單會導致庫存變成負號；實作上要如何解決這個問題呢？</p>
<h2 id="二從字面上了解悲觀鎖樂觀鎖">二、從字面上了解悲觀鎖、樂觀鎖<a class="h-anchor" href="#%e4%ba%8c%e5%be%9e%e5%ad%97%e9%9d%a2%e4%b8%8a%e4%ba%86%e8%a7%a3%e6%82%b2%e8%a7%80%e9%8e%96%e6%a8%82%e8%a7%80%e9%8e%96" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="-悲觀鎖-pessimistic-lock">😭 悲觀鎖 —Pessimistic Lock<a class="h-anchor" href="#-%e6%82%b2%e8%a7%80%e9%8e%96-pessimistic-lock" aria-label="這個段落的連結"></a></h3>
<p>就跟字面上的意思一樣，非常的悲觀；他認為 table 裡面的 data 非常的不安全，無時無刻都在變動，當一個SQL command(可以理解為搶購的用戶)獲得悲觀鎖後，其他的 SQL command 無法對這個 data 進行修改，直到悲觀鎖被釋放後才能執行。</p>
<blockquote>
<p>資料表中用 syncronized 實現的鎖均為悲觀鎖(ex：行鎖，表鎖，讀鎖，寫鎖)</p>
</blockquote>
<h3 class="h-nomark" id="-樂觀鎖--optimistic-lock">😃 樂觀鎖 — Optimistic Lock<a class="h-anchor" href="#-%e6%a8%82%e8%a7%80%e9%8e%96--optimistic-lock" aria-label="這個段落的連結"></a></h3>
<p>相比悲觀鎖，他認為 table 裡面的 data 變動頻率不會太頻繁，因此他會允許多個 SQL command 來操作 table；但樂觀並不代表不負責，通常會在 table 中增加一個 version 的欄位來做更新的確認。因此當 SQL command 想要變更欄位 data 時會先把之前取出 version 跟 table 現在的 version 做對比，如果相同就代表這段期間沒人修改可以執行；如果不同就會禁止這次的操作。</p>
<h2 id="三在-mysql-db-實作悲觀鎖">三、在 MySQL DB 實作悲觀鎖<a class="h-anchor" href="#%e4%b8%89%e5%9c%a8-mysql-db-%e5%af%a6%e4%bd%9c%e6%82%b2%e8%a7%80%e9%8e%96" aria-label="這個段落的連結"></a></h2>
<h3 id="a-建立模擬資料">A. 建立模擬資料：<a class="h-anchor" href="#a-%e5%bb%ba%e7%ab%8b%e6%a8%a1%e6%93%ac%e8%b3%87%e6%96%99" aria-label="這個段落的連結"></a></h3>
<p>先用 SQL command 建立一個簡單的 table 並插入 data</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span><span style="color:#6e7681"> </span>AUTO_INCREMENT,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>VARCHAR(<span style="color:#a5d6ff">45</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72">PRIMARY</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">KEY</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span>));<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">INSERT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">INTO</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span>)<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;iphone&#34;</span>,<span style="color:#a5d6ff">1</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/334f6b3cd81452fc56491e7360fd9265" rel="noopener">create_mock_item_table.sql</a>（GitHub Gist）</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-02.webp" width="585" height="221" alt="直接進去 MySQL command line 模式貼上去就好嚕" loading="lazy" decoding="async">
  <figcaption>直接進去 MySQL command line 模式貼上去就好嚕</figcaption>
</figure>

<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-03.webp" width="404" height="158" alt="用 SELECT * FROM `your_DB`.`items`; 確認一下資料是否建立成功" loading="lazy" decoding="async">
  <figcaption>用 <strong>SELECT * FROM `your_DB`.`items`;</strong> 確認一下資料是否建立成功</figcaption>
</figure>

<h3 id="b-用悲觀鎖解決-iphone-購買問題">B. 用悲觀鎖解決 iphone 購買問題<a class="h-anchor" href="#b-%e7%94%a8%e6%82%b2%e8%a7%80%e9%8e%96%e8%a7%a3%e6%b1%ba-iphone-%e8%b3%bc%e8%b2%b7%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p><strong>STEP 1</strong>：開一個視窗模擬 A 的行為，我們先在 A 購買前先將 <code>id=1</code> 的 data 加上悲觀鎖，此時這行 data 只有 A 可以操作</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-04.webp" width="353" height="56" alt="Transaction 的開始要用 begin;，不然 MySQL 會自動提交" loading="lazy" decoding="async">
  <figcaption>Transaction 的開始要用 <strong>begin;</strong>，不然 MySQL 會自動提交</figcaption>
</figure>

<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-05.webp" width="603" height="148" alt="接著用 select num from `your_DB`.`items` where id = 1 for update; 將 id = 1 的 data 加上悲觀鎖" loading="lazy" decoding="async">
  <figcaption>接著用 <strong>select num from `your_DB`.`items` where id = 1 for update; </strong>將 id = 1 的 data 加上悲觀鎖</figcaption>
</figure>

<p><strong>STEP 2</strong>：此時開另一個視窗模擬 B 的行為，一樣將 <code>id=1</code> 的 data 加上悲觀鎖</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-06.webp" width="618" height="99" alt="你會發現會進入等待的模式，要等 A 執行 commit 完成交易或是指令逾時才能繼續" loading="lazy" decoding="async">
  <figcaption>你會發現會進入等待的模式，要等 A 執行 commit 完成交易或是指令逾時才能繼續</figcaption>
</figure>

<p><strong>STEP 3</strong>：回到 A 的視窗，接著我們讓 A 執行購買的動作將 data 的 num 減一，然後檢視修改後的 data 是否符合預期，最後下 commit 結束這次交易</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-07.webp" width="585" height="281" alt="購買： update `your_DB`.`items` set num = num -1 where id = 1; ，檢視修改：select num from `your_DB`.`items` where id = 1; ，結束交易：commit;" loading="lazy" decoding="async">
  <figcaption>購買： <strong>update `your_DB`.`items` set num = num -1 where id = 1;</strong> ，檢視修改：<strong>select num from `your_DB`.`items` where id = 1; </strong>，結束交易：<strong>commit;</strong></figcaption>
</figure>

<p><strong>STEP 4</strong>：當 A 完成下單後回到 B 的視窗，你會發現因為 A 釋放了悲觀鎖，所以 B 結束等待並且獲得鎖，但 data 的 num 已經變成了 0 只能放棄購買</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-08.webp" width="585" height="143" alt="7.89 秒是等待獲得鎖的時間" loading="lazy" decoding="async">
  <figcaption>7.89 秒是等待獲得鎖的時間</figcaption>
</figure>

<h2 id="四在-mysql-db-實作樂觀鎖">四、在 MySQL DB 實作樂觀鎖<a class="h-anchor" href="#%e5%9b%9b%e5%9c%a8-mysql-db-%e5%af%a6%e4%bd%9c%e6%a8%82%e8%a7%80%e9%8e%96" aria-label="這個段落的連結"></a></h2>
<h3 id="a-建立模擬資料-1">A. 建立模擬資料：<a class="h-anchor" href="#a-%e5%bb%ba%e7%ab%8b%e6%a8%a1%e6%93%ac%e8%b3%87%e6%96%99-1" aria-label="這個段落的連結"></a></h3>
<p>先用 SQL command 建立一個簡單的 table 並插入 data，比起悲觀鎖的 table，我們多了一個 version 的欄位</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span><span style="color:#6e7681"> </span>AUTO_INCREMENT,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>VARCHAR(<span style="color:#a5d6ff">45</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#ff7b72">version</span><span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>INT<span style="color:#6e7681"> </span><span style="color:#ff7b72">NOT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">NULL</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#ff7b72">PRIMARY</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">KEY</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>id<span style="color:#ff7b72;font-weight:bold">`</span>));<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">INSERT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">INTO</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span>your_DB<span style="color:#ff7b72;font-weight:bold">`</span>.<span style="color:#ff7b72;font-weight:bold">`</span>happy_items<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#6e7681"> </span>(<span style="color:#ff7b72;font-weight:bold">`</span>name<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span>num<span style="color:#ff7b72;font-weight:bold">`</span>,<span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#ff7b72">version</span><span style="color:#ff7b72;font-weight:bold">`</span>)<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">&#34;iphone&#34;</span>,<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">0</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/4a8469117f075b3db7194106417c6765" rel="noopener">create_mock_happy_item_table.sql</a>（GitHub Gist）</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-09.webp" width="624" height="218" alt="直接進去 MySQL command line 模式貼上去就好嚕" loading="lazy" decoding="async">
  <figcaption>直接進去 MySQL command line 模式貼上去就好嚕</figcaption>
</figure>

<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-10.webp" width="427" height="143" alt="用 SELECT * FROM `your_DB`.`happy_items`; 確認一下資料是否建立成功" loading="lazy" decoding="async">
  <figcaption>用 <strong>SELECT * FROM `your_DB`.`happy_items`; </strong>確認一下資料是否建立成功</figcaption>
</figure>

<h3 id="b-用樂觀鎖解決-iphone-購買問題">B. 用樂觀鎖解決 iphone 購買問題<a class="h-anchor" href="#b-%e7%94%a8%e6%a8%82%e8%a7%80%e9%8e%96%e8%a7%a3%e6%b1%ba-iphone-%e8%b3%bc%e8%b2%b7%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<p><strong>思路</strong>：A 跟 B 在下單前會先將 <code>id=1</code> 的 data 找出來，然後 A 先買，此時會以 table 的 <code>id=1 和 version=0</code> 作為條件來更新 data，更新後 num 減一、version 加一變成 <code>id=1、num=0、version=1</code>；此時 B 購買時會因為用 <code>id=1 和 version=0</code>找不到 data 而無法更新。</p>
<p><strong>STEP 1</strong>：A 及 B 執行查詢的 SQL command 會取得相同的 iphone 庫存資訊</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-11.webp" width="621" height="143" alt="用 SELECT num,version FROM `your_DB`.`happy_items` where id = 1; 找出 iphone 庫存資訊" loading="lazy" decoding="async">
  <figcaption>用 <strong>SELECT num,version FROM `your_DB`.`happy_items` where id = 1;</strong> 找出 iphone 庫存資訊</figcaption>
</figure>

<p><strong>STEP 2</strong>：A 先購買了 iphone 來更新 data，然後檢視修改後的 data 是否符合預期</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-12.webp" width="978" height="370" alt="購買：update `your_DB`.`happy_items` set num = num -1 , version = version &#43; 1 where id = 1 and version = 0; ，檢視修改：SELECT num,version FROM `your_DB`.`happy_items` where id = 1;" loading="lazy" decoding="async">
  <figcaption>購買：<strong>update `your_DB`.`happy_items` set num = num -1 , version = version + 1 where id = 1 and version = 0; </strong>，檢視修改：<strong>SELECT num,version FROM `your_DB`.`happy_items` where id = 1;</strong></figcaption>
</figure>

<p><strong>STEP 3</strong>：此時 B 想要購買時就會無法更新 data，執行購買的 SQL command後你會發現沒有 Row 被更新</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/pessimistic-vs-optimistic-locking/images/pessimistic-vs-optimistic-locking-13.webp" width="978" height="75" alt="購買：update `your_DB`.`happy_items` set num = num -1 , version = version &#43; 1 where id = 1 and version = 0;" loading="lazy" decoding="async">
  <figcaption>購買：<strong>update `your_DB`.`happy_items` set num = num -1 , version = version + 1 where id = 1 and version = 0;</strong></figcaption>
</figure>

<h2 id="五悲觀鎖樂觀鎖優缺點">五、悲觀鎖、樂觀鎖優缺點<a class="h-anchor" href="#%e4%ba%94%e6%82%b2%e8%a7%80%e9%8e%96%e6%a8%82%e8%a7%80%e9%8e%96%e5%84%aa%e7%bc%ba%e9%bb%9e" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="-悲觀鎖--pessimistic-lock">😭 悲觀鎖 — Pessimistic Lock<a class="h-anchor" href="#-%e6%82%b2%e8%a7%80%e9%8e%96--pessimistic-lock" aria-label="這個段落的連結"></a></h3>
<p><strong>優點</strong>：使用資料庫 Transaction 的機制來強迫執行的順序</p>
<p><strong>缺點</strong>：一但加入 Transaction 的機制會導致其他的 SQL command 針對這個 data 除了查詢功能外全部卡死，如果這段 Transaction 執行時間較長會給使用者不良的體驗並造成系統吞吐量下降</p>
<h3 class="h-nomark" id="-樂觀鎖--optimistic-lock-1">😃 樂觀鎖 — Optimistic Lock<a class="h-anchor" href="#-%e6%a8%82%e8%a7%80%e9%8e%96--optimistic-lock-1" aria-label="這個段落的連結"></a></h3>
<p><strong>優點</strong>：因為沒有在資料庫加鎖，所以 SQL command 都會購對 data 進行操作，只有在更新 data 時才會做驗證；這樣就避免悲觀鎖導致的吞吐量下降的問題</p>
<p><strong>缺點</strong>：因為樂觀鎖是我們人為實現的，所以如果換一個業務場景可能會不適用，甚至可能因為其他的 SQL command 導致錯誤。</p>
<hr>
<p>參考資源：<a href="https://kknews.cc/zh-tw/news/z9aqnva.html">關於悲觀鎖和樂觀鎖的個人圖解</a>、<a href="https://www.jianshu.com/p/aaa12e90e9b9">商品秒殺問題</a></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
