<?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>MySQL｜林鼎淵 Dean Lin</title>
    <link>https://deanlin.net/tags/mysql/</link>
    <image><url>https://deanlin.net/images/author_hu_aa256fc934e9a932.jpg</url><title>MySQL｜林鼎淵 Dean Lin</title><link>https://deanlin.net/tags/mysql/</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>Sat, 25 May 2024 17:55:38 +0800</lastBuildDate>
    <atom:link href="https://deanlin.net/tags/mysql/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>MongoDB 與 MySQL 建立 Index 對 Read/Write 的效能影響</title>
      <link>https://deanlin.net/blog/202405/mongodb-vs-mysql-index-performance/</link>
      <pubDate>Sat, 25 May 2024 17:55:38 +0800</pubDate>
      <guid>https://deanlin.net/blog/202405/mongodb-vs-mysql-index-performance/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>儘管網路上有許多資料顯示 MongoDB 建立 Index 後的效能比 MySQL 好，但這篇文章我想要透過程式來驗證兩者的寫入、讀取、資料大小差異。</description>
      <media:content url="https://deanlin.net/blog/202405/mongodb-vs-mysql-index-performance/images/mongodb-vs-mysql-index-performance-og_hu_36b1bfdad50794f5.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p><a href="https://deanlin.net/blog/202405/database-bottlenecks/">上篇文章</a>分享了朋友遇到資料庫瓶頸的案例，收到許多網友的熱情回饋。</p>
<p>其中有個留言很有意思：「如果在 MongoDB 建立 Index，那麼資料寫入的速度會比 MySQL 快很多嗎（有建立相同 Index 的情況下）？」</p>
<p>儘管網路上有許多資料顯示 MongoDB 建立 Index 後的效能比 MySQL 好，但這篇文章我想要透過程式來驗證兩者寫入、讀取、資料大小的差異。</p>
<p><img src="https://deanlin.net/blog/202405/mongodb-vs-mysql-index-performance/images/mongodb-vs-mysql-index-performance-01.webp" width="1400" height="787" alt="MongoDB 與 MySQL 建立 Index 對 Read/Write 的效能影響（圖 1）" fetchpriority="high" decoding="async"></p>
<blockquote>
<p>非常建議看到最後，因為測試結果可能跟你想的不一樣。</p>
</blockquote>
<pre tabindex="0"><code>文章大綱

▋ 測試參數說明
▋ 前置作業 1 ── docker image pull
▋ 前置作業 2 ── 安裝 Python 相關套件
▋ Script 設計說明
▋ MySQL 測試方案
▋ MongoDB 測試方案 1 ── 單筆資料的 Index 設計
▋ MongoDB 測試方案 2 ── Time Series Collection
▋ MongoDB 測試方案 3 ── Embedded Documents
▋ 結論：想象跟實際的落差
</code></pre><h2 id="-測試參數說明">測試參數說明<a class="h-anchor" href="#-%e6%b8%ac%e8%a9%a6%e5%8f%83%e6%95%b8%e8%aa%aa%e6%98%8e" aria-label="這個段落的連結"></a></h2>
<p>這邊我以<a href="https://deanlin.net/blog/202405/database-bottlenecks/">上一篇</a>談到的商品統計圖表來做設計，因為主要比對效能上的差異，所以用的資料量級比較小（否則某些情境會跑到天荒地老）。</p>
<ul>
<li>商品種類: 2000 筆</li>
<li>統計頻率: 1 分鐘</li>
<li>時間長度: 8 小時</li>
<li>總資料量: 96 萬筆（2000 * 8 * 60）</li>
<li>電腦規格: Apple M1 Max</li>
</ul>
<hr>
<h2 id="-前置作業-1--docker-image-pull">前置作業 1 ── docker image pull<a class="h-anchor" href="#-%e5%89%8d%e7%bd%ae%e4%bd%9c%e6%a5%ad-1--docker-image-pull" aria-label="這個段落的連結"></a></h2>
<p>如果你有興趣，可以跟著文章步驟一起實作，為求公平，我這邊使用最新版的 MongoDB 跟 MySQL 來做測試（當然也可以直接滑到最後看實驗結果）。</p>
<p>我也有把文章的 Code 推到 <a href="https://github.com/dean9703111/mongodb-mysql-index-performance">Github</a> 讓大家參考。</p>
<p>如果有使用 Docker，可以使用下面的 <code>docker-compose.yaml</code> 取的對應的資源。</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-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7ee787">version</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;1.1&#39;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">services</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">mongodb</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">mongodb/mongodb-community-server:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">container_name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">mongodb</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;27017:27017&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">volumes</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">mongodb_data:/data/db</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">networks</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">mynetwork</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">mongo-express</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">mongo-express:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">container_name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">mongo-express</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;8081:8081&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">environment</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">ME_CONFIG_MONGODB_SERVER</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">mongodb</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">ME_CONFIG_BASICAUTH_USERNAME</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">root</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">ME_CONFIG_BASICAUTH_PASSWORD</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">example</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">networks</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">mynetwork</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">mysql</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">mysql/mysql-server:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">container_name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">mysql</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;3306:3306&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">environment</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">MYSQL_ROOT_PASSWORD</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">example</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">MYSQL_DATABASE</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">exampledb</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">volumes</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">mysql_data:/var/lib/mysql</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">./mysql-conf.d:/etc/mysql/conf.d</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">networks</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">mynetwork</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">phpmyadmin</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">image</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">phpmyadmin/phpmyadmin:latest</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">container_name</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">phpmyadmin</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">depends_on</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">mysql</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">ports</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">&#34;8080:80&#34;</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">environment</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">PMA_HOST</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">mysql</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span><span style="color:#7ee787">PMA_PORT</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">3306</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">networks</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">      </span>- <span style="color:#a5d6ff">mynetwork</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">volumes</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">mongodb_data</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">mysql_data</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#7ee787">networks</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span><span style="color:#7ee787">mynetwork</span>:<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">    </span><span style="color:#7ee787">driver</span>:<span style="color:#6e7681"> </span><span style="color:#a5d6ff">bridge</span><span style="color:#6e7681">
</span></span></span></code></pre></div><p>下面是對應的 GUI 連結:</p>
<ul>
<li><strong>phpMyAdmin</strong>（帳號:root、密碼:example）: <a href="http://localhost:8080">http://localhost:8080</a></li>
<li><strong>Mongo Express</strong>（帳號:root、密碼:example）: <a href="http://localhost:8081">http://localhost:8081</a></li>
</ul>
<hr>
<h2 id="-前置作業-2--安裝-python-相關套件">前置作業 2 ── 安裝 Python 相關套件<a class="h-anchor" href="#-%e5%89%8d%e7%bd%ae%e4%bd%9c%e6%a5%ad-2--%e5%ae%89%e8%a3%9d-python-%e7%9b%b8%e9%97%9c%e5%a5%97%e4%bb%b6" aria-label="這個段落的連結"></a></h2>
<p>我使用的 Python 版本為: 3.10.14</p>
<p>這邊我使用 Python 來撰寫測試用的 Script，若你尚未安裝，可以直接去官網下載: <a href="https://www.python.org/downloads/">https://www.python.org/downloads/</a></p>
<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-bash" data-lang="bash"><span style="display:flex;"><span>pip install pymysql pymongo cryptography
</span></span></code></pre></div><hr>
<h2 id="-script-設計說明">Script 設計說明<a class="h-anchor" href="#-script-%e8%a8%ad%e8%a8%88%e8%aa%aa%e6%98%8e" aria-label="這個段落的連結"></a></h2>
<p>我總共寫了 4 個測試腳本，MySQL 1 個，MongoDB 3 個，基本上每個腳本都會做如下任務，差別在於資料結構設計不同。</p>
<ol>
<li>與資料庫（MySQL/MongoDB）建立連線</li>
<li>建立 Table/Collection，並在「商品名稱(product_name)、紀錄時間(timestamp)」的欄位加上 index</li>
<li>寫入資料，模擬為期 8 小時、1 分鐘寫入 2000 筆資料的情境。</li>
<li>查詢效能測試，會亂數產生 100 次不同參數的搜尋條件。</li>
<li>印出寫入時間/寫入總量/平均查詢時間</li>
</ol>
<hr>
<h2 id="-mysql-測試方案">MySQL 測試方案<a class="h-anchor" href="#-mysql-%e6%b8%ac%e8%a9%a6%e6%96%b9%e6%a1%88" aria-label="這個段落的連結"></a></h2>
<p>MySQL 版本: 8.0.32</p>
<p>這個腳本沒什麼特別的，只是在「商品名稱(product_name)、紀錄時間(timestamp)」加上了 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-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">pymysql</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">random</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">time</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">datetime</span> <span style="color:#ff7b72">import</span> datetime, timedelta
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># MySQL 連接設定</span>
</span></span><span style="display:flex;"><span>conn <span style="color:#ff7b72;font-weight:bold">=</span> pymysql<span style="color:#ff7b72;font-weight:bold">.</span>connect(
</span></span><span style="display:flex;"><span>    host<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;localhost&#39;</span>,
</span></span><span style="display:flex;"><span>    user<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;root&#39;</span>,
</span></span><span style="display:flex;"><span>    password<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;example&#39;</span>,
</span></span><span style="display:flex;"><span>    database<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;exampledb&#39;</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>cursor <span style="color:#ff7b72;font-weight:bold">=</span> conn<span style="color:#ff7b72;font-weight:bold">.</span>cursor()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 建立 DB 和 Table</span>
</span></span><span style="display:flex;"><span>sql_commands <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#34;CREATE DATABASE IF NOT EXISTS exampledb;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#34;USE exampledb;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    CREATE TABLE IF NOT EXISTS product_stats (
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        id BIGINT AUTO_INCREMENT PRIMARY KEY,
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        product_name VARCHAR(255),
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        timestamp DATETIME,
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        data TEXT,
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        INDEX idx_product_name_timestamp (product_name, timestamp)
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    );
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    &#34;&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#34;TRUNCATE product_stats;&#34;</span>
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 執行 SQL 指令</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> command <span style="color:#ff7b72;font-weight:bold">in</span> sql_commands:
</span></span><span style="display:flex;"><span>    cursor<span style="color:#ff7b72;font-weight:bold">.</span>execute(command)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>conn<span style="color:#ff7b72;font-weight:bold">.</span>commit()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 寫入資料</span>
</span></span><span style="display:flex;"><span>start_time <span style="color:#ff7b72;font-weight:bold">=</span> datetime<span style="color:#ff7b72;font-weight:bold">.</span>now()
</span></span><span style="display:flex;"><span>insert_batch_size <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2000</span>  <span style="color:#8b949e;font-style:italic"># 每批次寫入數據量</span>
</span></span><span style="display:flex;"><span>product_size <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2000</span> <span style="color:#8b949e;font-style:italic"># 要測試的商品總量</span>
</span></span><span style="display:flex;"><span>total_records <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>  <span style="color:#8b949e;font-style:italic"># 紀錄寫入的數據總量</span>
</span></span><span style="display:flex;"><span>time_range <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">8</span> <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">60</span> <span style="color:#8b949e;font-style:italic"># 總時間 8 hours</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 開始計時</span>
</span></span><span style="display:flex;"><span>insert_start_time <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 建立數據並批量寫入</span>
</span></span><span style="display:flex;"><span>batch <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> minute <span style="color:#ff7b72;font-weight:bold">in</span> range(time_range):
</span></span><span style="display:flex;"><span>    timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_time <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span>minute)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(product_size):  <span style="color:#8b949e;font-style:italic"># 每分鐘要寫入的商品紀錄</span>
</span></span><span style="display:flex;"><span>        product_name <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;商品名稱</span><span style="color:#a5d6ff">{</span>i<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>  <span style="color:#8b949e;font-style:italic"># 模擬 product_size 種不同商品</span>
</span></span><span style="display:flex;"><span>        stats <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;統計資料</span><span style="color:#a5d6ff">{</span>i<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>
</span></span><span style="display:flex;"><span>        batch<span style="color:#ff7b72;font-weight:bold">.</span>append((product_name, timestamp, stats))
</span></span><span style="display:flex;"><span>        total_records <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># 批量寫入</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> len(batch) <span style="color:#ff7b72;font-weight:bold">&gt;=</span> insert_batch_size:
</span></span><span style="display:flex;"><span>            cursor<span style="color:#ff7b72;font-weight:bold">.</span>executemany(
</span></span><span style="display:flex;"><span>                <span style="color:#a5d6ff">&#34;INSERT INTO product_stats (product_name, timestamp, data) VALUES (</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">, </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">, </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">)&#34;</span>,
</span></span><span style="display:flex;"><span>                batch
</span></span><span style="display:flex;"><span>            )
</span></span><span style="display:flex;"><span>            conn<span style="color:#ff7b72;font-weight:bold">.</span>commit()
</span></span><span style="display:flex;"><span>            batch <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 寫入剩餘的數據</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> batch:
</span></span><span style="display:flex;"><span>    cursor<span style="color:#ff7b72;font-weight:bold">.</span>executemany(
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;INSERT INTO product_stats (product_name, timestamp, data) VALUES (</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">, </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">, </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">)&#34;</span>,
</span></span><span style="display:flex;"><span>        batch
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    conn<span style="color:#ff7b72;font-weight:bold">.</span>commit()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 計時結束</span>
</span></span><span style="display:flex;"><span>insert_end_time <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 查詢效能測試</span>
</span></span><span style="display:flex;"><span>query_times <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">100</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 隨機生成查詢的時間區間</span>
</span></span><span style="display:flex;"><span>    start_timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_time <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span>random<span style="color:#ff7b72;font-weight:bold">.</span>randint(<span style="color:#a5d6ff">0</span>, time_range<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">30</span>))  <span style="color:#8b949e;font-style:italic"># 最大值為 - 30 為 30 分鐘</span>
</span></span><span style="display:flex;"><span>    end_timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_timestamp <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">30</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 查詢多筆商品名稱在特定時間範圍內的資料</span>
</span></span><span style="display:flex;"><span>    product_names <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;商品名稱</span><span style="color:#a5d6ff">{</span>random<span style="color:#ff7b72;font-weight:bold">.</span>randint(<span style="color:#a5d6ff">1</span>, product_size)<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span> <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">10</span>)]  <span style="color:#8b949e;font-style:italic"># 隨機查詢 10 個商品</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 使用 IN 進行多商品名稱查詢</span>
</span></span><span style="display:flex;"><span>    format_strings <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;,&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join([<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;&#39;</span><span style="color:#a5d6ff">{</span>name<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#39;&#34;</span> <span style="color:#ff7b72">for</span> name <span style="color:#ff7b72;font-weight:bold">in</span> product_names])
</span></span><span style="display:flex;"><span>    query <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;SELECT * FROM product_stats WHERE product_name IN (</span><span style="color:#a5d6ff">{</span>format_strings<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">) AND timestamp BETWEEN &#39;</span><span style="color:#a5d6ff">{</span>start_timestamp<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#39; AND &#39;</span><span style="color:#a5d6ff">{</span>end_timestamp<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#39;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 執行查詢</span>
</span></span><span style="display:flex;"><span>    start_query <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>    cursor<span style="color:#ff7b72;font-weight:bold">.</span>execute(query)
</span></span><span style="display:flex;"><span>    rows <span style="color:#ff7b72;font-weight:bold">=</span> cursor<span style="color:#ff7b72;font-weight:bold">.</span>fetchall()
</span></span><span style="display:flex;"><span>    end_query <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>    query_times<span style="color:#ff7b72;font-weight:bold">.</span>append(end_query <span style="color:#ff7b72;font-weight:bold">-</span> start_query)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MySQL 寫入時間: </span><span style="color:#a5d6ff">{</span>insert_end_time <span style="color:#ff7b72;font-weight:bold">-</span> insert_start_time<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 秒&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MySQL 寫入總量: </span><span style="color:#a5d6ff">{</span>total_records<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 條&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 計算平均查詢時間</span>
</span></span><span style="display:flex;"><span>average_query_time <span style="color:#ff7b72;font-weight:bold">=</span> sum(query_times) <span style="color:#ff7b72;font-weight:bold">/</span> len(query_times)
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MySQL 平均查詢時間: </span><span style="color:#a5d6ff">{</span>average_query_time<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 秒&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cursor<span style="color:#ff7b72;font-weight:bold">.</span>close()
</span></span><span style="display:flex;"><span>conn<span style="color:#ff7b72;font-weight:bold">.</span>close()
</span></span></code></pre></div><p>下面為 MySQL 測試結果:</p>
<ul>
<li><strong>寫入時間</strong>: 11.6187 秒</li>
<li><strong>平均查詢時間</strong>: 0.0058 秒</li>
<li><strong>資料大小</strong>: 151.4 MB</li>
</ul>
<hr>
<h2 id="-mongodb-測試方案-1--單筆資料的-index-設計">MongoDB 測試方案 1 ── 單筆資料的 Index 設計<a class="h-anchor" href="#-mongodb-%e6%b8%ac%e8%a9%a6%e6%96%b9%e6%a1%88-1--%e5%96%ae%e7%ad%86%e8%b3%87%e6%96%99%e7%9a%84-index-%e8%a8%ad%e8%a8%88" aria-label="這個段落的連結"></a></h2>
<p>MongoDB 版本: 7.0.9</p>
<p>這個腳本是可以直接與 MySQL 比對的版本，因為都是採 row data 的概念，僅在「商品名稱(product_name)、紀錄時間(timestamp)」加上了 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-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;product_name&#34;</span> : <span style="color:#a5d6ff">&#34;商品名稱2&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;timestamp&#34;</span> : <span style="color:#f85149">ISODate(</span><span style="color:#a5d6ff">&#34;2024-05-25T15:24:46.492+0000&#34;</span><span style="color:#f85149">)</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;data&#34;</span> : <span style="color:#a5d6ff">&#34;統計資料2&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><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-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">pymongo</span> <span style="color:#ff7b72">import</span> MongoClient, ASCENDING
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">random</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">time</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">datetime</span> <span style="color:#ff7b72">import</span> datetime, timedelta
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># MongoDB 連接設置</span>
</span></span><span style="display:flex;"><span>client <span style="color:#ff7b72;font-weight:bold">=</span> MongoClient(<span style="color:#a5d6ff">&#39;mongodb://localhost:27017/&#39;</span>)
</span></span><span style="display:flex;"><span>db <span style="color:#ff7b72;font-weight:bold">=</span> client<span style="color:#ff7b72;font-weight:bold">.</span>testdb
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 清空集合並創建新集合</span>
</span></span><span style="display:flex;"><span>db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>drop()
</span></span><span style="display:flex;"><span>db<span style="color:#ff7b72;font-weight:bold">.</span>create_collection(<span style="color:#a5d6ff">&#39;product_stats&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 加入 index</span>
</span></span><span style="display:flex;"><span>db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>create_index([(<span style="color:#a5d6ff">&#34;product_name&#34;</span>, ASCENDING), (<span style="color:#a5d6ff">&#34;timestamp&#34;</span>, ASCENDING)])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 寫入數據</span>
</span></span><span style="display:flex;"><span>start_time <span style="color:#ff7b72;font-weight:bold">=</span> datetime<span style="color:#ff7b72;font-weight:bold">.</span>now()
</span></span><span style="display:flex;"><span>insert_batch_size <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2000</span>  <span style="color:#8b949e;font-style:italic"># 每批次寫入數據量</span>
</span></span><span style="display:flex;"><span>product_size <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2000</span> <span style="color:#8b949e;font-style:italic"># 要測試的商品總量</span>
</span></span><span style="display:flex;"><span>total_records <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>  <span style="color:#8b949e;font-style:italic"># 記錄寫入的數據總量</span>
</span></span><span style="display:flex;"><span>time_range <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">8</span> <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">60</span> <span style="color:#8b949e;font-style:italic"># 總時間 8 hours</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 開始計時</span>
</span></span><span style="display:flex;"><span>insert_start_time <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 建立數據並批量寫入</span>
</span></span><span style="display:flex;"><span>batch <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> minute <span style="color:#ff7b72;font-weight:bold">in</span> range(time_range):  <span style="color:#8b949e;font-style:italic"># 8 小時</span>
</span></span><span style="display:flex;"><span>    timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_time <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span>minute)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(product_size):  <span style="color:#8b949e;font-style:italic"># 每分鐘要寫入的商品紀錄</span>
</span></span><span style="display:flex;"><span>        product_name <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;商品名稱</span><span style="color:#a5d6ff">{</span>i<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>  <span style="color:#8b949e;font-style:italic"># 模擬 product_size 種不同商品</span>
</span></span><span style="display:flex;"><span>        stats <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;統計資料</span><span style="color:#a5d6ff">{</span>i<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>
</span></span><span style="display:flex;"><span>        batch<span style="color:#ff7b72;font-weight:bold">.</span>append({
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;product_name&#34;</span>: product_name,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;timestamp&#34;</span>: timestamp,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;data&#34;</span>: stats
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>        total_records <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># 批量寫入</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> len(batch) <span style="color:#ff7b72;font-weight:bold">&gt;=</span> insert_batch_size:
</span></span><span style="display:flex;"><span>            db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>insert_many(batch)
</span></span><span style="display:flex;"><span>            batch<span style="color:#ff7b72;font-weight:bold">.</span>clear()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 寫入剩餘的數據</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> batch:
</span></span><span style="display:flex;"><span>    db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>insert_many(batch)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 計時結束</span>
</span></span><span style="display:flex;"><span>insert_end_time <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 查詢效能測試</span>
</span></span><span style="display:flex;"><span>query_times <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">100</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 隨機生成查詢的時間區間</span>
</span></span><span style="display:flex;"><span>    start_timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_time <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span>random<span style="color:#ff7b72;font-weight:bold">.</span>randint(<span style="color:#a5d6ff">0</span>, time_range<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">30</span>))  <span style="color:#8b949e;font-style:italic"># 最大值為 - 30 為 30 分鐘</span>
</span></span><span style="display:flex;"><span>    end_timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_timestamp <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">30</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    product_names <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;商品名稱</span><span style="color:#a5d6ff">{</span>random<span style="color:#ff7b72;font-weight:bold">.</span>randint(<span style="color:#a5d6ff">1</span>, product_size)<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span> <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">10</span>)]  <span style="color:#8b949e;font-style:italic"># 隨機查詢 10 個商品</span>
</span></span><span style="display:flex;"><span>    query <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;product_name&#34;</span>: {<span style="color:#a5d6ff">&#34;$in&#34;</span>: product_names},
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;timestamp&#34;</span>: {<span style="color:#a5d6ff">&#34;$gt&#34;</span>: start_timestamp, <span style="color:#a5d6ff">&#34;$lte&#34;</span>: end_timestamp}
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 執行查詢</span>
</span></span><span style="display:flex;"><span>    start_query <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>    result <span style="color:#ff7b72;font-weight:bold">=</span> db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>find(query)
</span></span><span style="display:flex;"><span>    end_query <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>    query_times<span style="color:#ff7b72;font-weight:bold">.</span>append(end_query <span style="color:#ff7b72;font-weight:bold">-</span> start_query)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MongoDB 寫入時間: </span><span style="color:#a5d6ff">{</span>insert_end_time <span style="color:#ff7b72;font-weight:bold">-</span> insert_start_time<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 秒&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MongoDB 寫入總量: </span><span style="color:#a5d6ff">{</span>total_records<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 條&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 計算平均查詢時間</span>
</span></span><span style="display:flex;"><span>average_query_time <span style="color:#ff7b72;font-weight:bold">=</span> sum(query_times) <span style="color:#ff7b72;font-weight:bold">/</span> len(query_times)
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MongoDB 平均查詢時間: </span><span style="color:#a5d6ff">{</span>average_query_time<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 秒&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>client<span style="color:#ff7b72;font-weight:bold">.</span>close()
</span></span></code></pre></div><p>下面為 MongoDB 測試方案 1 的結果:</p>
<ul>
<li><strong>寫入時間</strong>: 6.8597 秒</li>
<li><strong>平均查詢時間</strong>: 6.3467e-06 秒</li>
<li><strong>資料大小</strong>: 97.8 MB</li>
</ul>
<hr>
<h2 id="-mongodb-測試方案-2--time-series-collection">MongoDB 測試方案 2 ── Time Series Collection<a class="h-anchor" href="#-mongodb-%e6%b8%ac%e8%a9%a6%e6%96%b9%e6%a1%88-2--time-series-collection" aria-label="這個段落的連結"></a></h2>
<p>第二個方案則是採用 MongoDB Time Series Collection 的設計，設定如下：</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-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;product_stats&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">timeseries={</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;timeField&#34;</span>: <span style="color:#a5d6ff">&#34;timestamp&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;metaField&#34;</span>: <span style="color:#a5d6ff">&#34;product_name&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#7ee787">&#34;granularity&#34;</span>: <span style="color:#a5d6ff">&#34;minutes&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span><span style="color:#f85149">}</span>
</span></span></code></pre></div><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-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">pymongo</span> <span style="color:#ff7b72">import</span> MongoClient, ASCENDING
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">random</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">time</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">datetime</span> <span style="color:#ff7b72">import</span> datetime, timedelta
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># MongoDB 連接設置</span>
</span></span><span style="display:flex;"><span>client <span style="color:#ff7b72;font-weight:bold">=</span> MongoClient(<span style="color:#a5d6ff">&#39;mongodb://localhost:27017/&#39;</span>)
</span></span><span style="display:flex;"><span>db <span style="color:#ff7b72;font-weight:bold">=</span> client<span style="color:#ff7b72;font-weight:bold">.</span>testdb
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 清空集合並創建新集合</span>
</span></span><span style="display:flex;"><span>db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>drop()
</span></span><span style="display:flex;"><span>db<span style="color:#ff7b72;font-weight:bold">.</span>create_collection(
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#34;product_stats&#34;</span>,
</span></span><span style="display:flex;"><span>    timeseries<span style="color:#ff7b72;font-weight:bold">=</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;timeField&#34;</span>: <span style="color:#a5d6ff">&#34;timestamp&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;metaField&#34;</span>: <span style="color:#a5d6ff">&#34;product_name&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;granularity&#34;</span>: <span style="color:#a5d6ff">&#34;minutes&#34;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 寫入數據</span>
</span></span><span style="display:flex;"><span>start_time <span style="color:#ff7b72;font-weight:bold">=</span> datetime<span style="color:#ff7b72;font-weight:bold">.</span>now()
</span></span><span style="display:flex;"><span>insert_batch_size <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2000</span>  <span style="color:#8b949e;font-style:italic"># 每批次寫入數據量</span>
</span></span><span style="display:flex;"><span>product_size <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">2000</span> <span style="color:#8b949e;font-style:italic"># 要測試的商品總量</span>
</span></span><span style="display:flex;"><span>total_records <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>  <span style="color:#8b949e;font-style:italic"># 記錄寫入的數據總量</span>
</span></span><span style="display:flex;"><span>time_range <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">8</span> <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">60</span> <span style="color:#8b949e;font-style:italic"># 總時間 8 hours</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 開始計時</span>
</span></span><span style="display:flex;"><span>insert_start_time <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 建立數據並批量寫入</span>
</span></span><span style="display:flex;"><span>batch <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> minute <span style="color:#ff7b72;font-weight:bold">in</span> range(time_range):  <span style="color:#8b949e;font-style:italic"># 8 小時</span>
</span></span><span style="display:flex;"><span>    timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_time <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span>minute)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(product_size):  <span style="color:#8b949e;font-style:italic"># 每分鐘要寫入的商品紀錄</span>
</span></span><span style="display:flex;"><span>        product_name <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;商品名稱</span><span style="color:#a5d6ff">{</span>i<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>  <span style="color:#8b949e;font-style:italic"># 模擬 product_size 種不同商品</span>
</span></span><span style="display:flex;"><span>        data <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;統計資料</span><span style="color:#a5d6ff">{</span>i<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        batch<span style="color:#ff7b72;font-weight:bold">.</span>append({
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;timestamp&#34;</span>: timestamp,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;product_name&#34;</span>: product_name,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;data&#34;</span>: data
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>        total_records <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># 批量寫入</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> len(batch) <span style="color:#ff7b72;font-weight:bold">&gt;=</span> insert_batch_size:
</span></span><span style="display:flex;"><span>            db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>insert_many(batch)
</span></span><span style="display:flex;"><span>            batch<span style="color:#ff7b72;font-weight:bold">.</span>clear()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 寫入剩餘的數據</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> batch:
</span></span><span style="display:flex;"><span>    db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>insert_many(batch)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 計時結束</span>
</span></span><span style="display:flex;"><span>insert_end_time <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 查詢性能測試</span>
</span></span><span style="display:flex;"><span>query_times <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">100</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 隨機生成查詢的時間區間</span>
</span></span><span style="display:flex;"><span>    start_timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_time <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span>random<span style="color:#ff7b72;font-weight:bold">.</span>randint(<span style="color:#a5d6ff">0</span>, time_range<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">30</span>))  <span style="color:#8b949e;font-style:italic"># 最大值為 - 30 為 30 分鐘</span>
</span></span><span style="display:flex;"><span>    end_timestamp <span style="color:#ff7b72;font-weight:bold">=</span> start_timestamp <span style="color:#ff7b72;font-weight:bold">+</span> timedelta(minutes<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">30</span>)
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    product_names <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;商品名稱</span><span style="color:#a5d6ff">{</span>random<span style="color:#ff7b72;font-weight:bold">.</span>randint(<span style="color:#a5d6ff">1</span>, product_size)<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span> <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">10</span>)]  <span style="color:#8b949e;font-style:italic"># 隨機查詢 10 個商品</span>
</span></span><span style="display:flex;"><span>    query <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;product_name&#34;</span>: {<span style="color:#a5d6ff">&#34;$in&#34;</span>: product_names},
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;timestamp&#34;</span>: {<span style="color:#a5d6ff">&#34;$gt&#34;</span>: start_timestamp, <span style="color:#a5d6ff">&#34;$lte&#34;</span>: end_timestamp}
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 執行查詢</span>
</span></span><span style="display:flex;"><span>    start_query <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>    result <span style="color:#ff7b72;font-weight:bold">=</span> db<span style="color:#ff7b72;font-weight:bold">.</span>product_stats<span style="color:#ff7b72;font-weight:bold">.</span>find(query)
</span></span><span style="display:flex;"><span>    end_query <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>    query_times<span style="color:#ff7b72;font-weight:bold">.</span>append(end_query <span style="color:#ff7b72;font-weight:bold">-</span> start_query)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MongoDB Time Series Collection 寫入時間: </span><span style="color:#a5d6ff">{</span>insert_end_time <span style="color:#ff7b72;font-weight:bold">-</span> insert_start_time<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 秒&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MongoDB Time Series Collection 寫入總量: </span><span style="color:#a5d6ff">{</span>total_records<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 條&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 計算平均查詢時間</span>
</span></span><span style="display:flex;"><span>average_query_time <span style="color:#ff7b72;font-weight:bold">=</span> sum(query_times) <span style="color:#ff7b72;font-weight:bold">/</span> len(query_times)
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;MongoDB Time Series Collection 平均查詢時間: </span><span style="color:#a5d6ff">{</span>average_query_time<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff"> 秒&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>client<span style="color:#ff7b72;font-weight:bold">.</span>close()
</span></span></code></pre></div><p>下面為 MongoDB 測試方案 2 的結果:</p>
<ul>
<li><strong>寫入時間</strong>: 31.0204 秒</li>
<li><strong>平均查詢時間</strong>: 6.7949e-06 秒</li>
<li><strong>資料大小</strong>: 53.1 MB</li>
</ul>
<h2 id="mongodb-測試方案-3--embedded-documents">MongoDB 測試方案 3 ── Embedded Documents<a class="h-anchor" href="#mongodb-%e6%b8%ac%e8%a9%a6%e6%96%b9%e6%a1%88-3--embedded-documents" aria-label="這個段落的連結"></a></h2>
<p>第三個方案是上篇文章的最終方案，將統計數據紀錄到商品名稱（product_name）底下，並對「商品名稱(product_name)、紀錄時間(timestamp)」加上了 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-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;_id&#34;</span> : <span style="color:#f85149">ObjectId(</span><span style="color:#a5d6ff">&#34;6651a4d6329cedfc74f40dde&#34;</span><span style="color:#f85149">)</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;product_name&#34;</span> : <span style="color:#a5d6ff">&#34;商品名稱0&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;stats&#34;</span> : [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;timestamp&#34;</span> : <span style="color:#f85149">ISODate(</span><span style="color:#a5d6ff">&#34;2024-05-25T16:44:06.728+0000&#34;</span><span style="color:#f85149">)</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;data&#34;</span> : <span style="color:#a5d6ff">&#34;統計資料0&#34;</span>
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;timestamp&#34;</span> : <span style="color:#f85149">ISODate(</span><span style="color:#a5d6ff">&#34;2024-05-25T16:45:06.728+0000&#34;</span><span style="color:#f85149">)</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;data&#34;</span> : <span style="color:#a5d6ff">&#34;統計資料0&#34;</span>
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">...</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#f85149">from</span> <span style="color:#f85149">pymongo</span> <span style="color:#f85149">import</span> <span style="color:#f85149">MongoClient,</span> <span style="color:#f85149">ASCENDING,</span> <span style="color:#f85149">UpdateOne</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">import</span> <span style="color:#f85149">random</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">import</span> <span style="color:#f85149">time</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">from</span> <span style="color:#f85149">datetime</span> <span style="color:#f85149">import</span> <span style="color:#f85149">datetime,</span> <span style="color:#f85149">timedelta</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">MongoDB</span> <span style="color:#f85149">連接設置</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">client</span> <span style="color:#f85149">=</span> <span style="color:#f85149">MongoClient(&#39;mongodb:</span><span style="color:#8b949e;font-style:italic">//localhost:27017/&#39;)
</span></span></span><span style="display:flex;"><span><span style="color:#f85149">db</span> <span style="color:#f85149">=</span> <span style="color:#f85149">client.testdb</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">清空集合並創建新集合</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">db.product_stats.drop()</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">db.create_collection(&#39;product_stats&#39;)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">加入</span> <span style="color:#f85149">index</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">db.product_stats.create_index(</span>[<span style="color:#f85149">(</span><span style="color:#a5d6ff">&#34;product_name&#34;</span>, <span style="color:#f85149">ASCENDING)</span>, <span style="color:#f85149">(</span><span style="color:#a5d6ff">&#34;stats.timestamp&#34;</span>, <span style="color:#f85149">ASCENDING)</span>]<span style="color:#f85149">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">寫入數據</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">start_time</span> <span style="color:#f85149">=</span> <span style="color:#f85149">datetime.now()</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">insert_batch_size</span> <span style="color:#f85149">=</span> <span style="color:#a5d6ff">2000</span>  <span style="color:#f85149">#</span> <span style="color:#f85149">每批次寫入數據量</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">product_size</span> <span style="color:#f85149">=</span> <span style="color:#a5d6ff">2000</span> <span style="color:#f85149">#</span> <span style="color:#f85149">要測試的商品總量</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">total_records</span> <span style="color:#f85149">=</span> <span style="color:#a5d6ff">0</span>  <span style="color:#f85149">#</span> <span style="color:#f85149">記錄寫入的數據總量</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">time_range</span> <span style="color:#f85149">=</span> <span style="color:#a5d6ff">8</span> <span style="color:#f85149">*</span> <span style="color:#a5d6ff">60</span> <span style="color:#f85149">#</span> <span style="color:#f85149">總時間</span> <span style="color:#a5d6ff">8</span> <span style="color:#f85149">hours</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">開始計時</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">insert_start_time</span> <span style="color:#f85149">=</span> <span style="color:#f85149">time.time()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">建立數據並批量寫入</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">product_dict</span> <span style="color:#f85149">=</span> {}
</span></span><span style="display:flex;"><span><span style="color:#f85149">for</span> <span style="color:#f85149">minute</span> <span style="color:#f85149">in</span> <span style="color:#f85149">range(time_range):</span>  <span style="color:#f85149">#</span> <span style="color:#a5d6ff">8</span> <span style="color:#f85149">小時</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">timestamp</span> <span style="color:#f85149">=</span> <span style="color:#f85149">start_time</span> <span style="color:#f85149">+</span> <span style="color:#f85149">timedelta(minutes=minute)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">for</span> <span style="color:#f85149">i</span> <span style="color:#f85149">in</span> <span style="color:#f85149">range(product_size):</span>  <span style="color:#f85149">#</span> <span style="color:#f85149">每分鐘要寫入的商品紀錄</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">product_name</span> <span style="color:#f85149">=</span> <span style="color:#f85149">f</span><span style="color:#a5d6ff">&#34;商品名稱{i}&#34;</span>  <span style="color:#f85149">#</span> <span style="color:#f85149">模擬</span> <span style="color:#f85149">product_size</span> <span style="color:#f85149">種不同商品</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">stats</span> <span style="color:#f85149">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;timestamp&#34;</span>: <span style="color:#f85149">timestamp</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;data&#34;</span>: <span style="color:#f85149">f</span><span style="color:#a5d6ff">&#34;統計資料{i}&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">if</span> <span style="color:#f85149">product_name</span> <span style="color:#f85149">not</span> <span style="color:#f85149">in</span> <span style="color:#f85149">product_dict:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">product_dict</span>[<span style="color:#f85149">product_name</span>] <span style="color:#f85149">=</span> []
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">product_dict</span>[<span style="color:#f85149">product_name</span>]<span style="color:#f85149">.append(stats)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">total_records</span> <span style="color:#f85149">+=</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">#</span> <span style="color:#f85149">批量更新</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">if</span> <span style="color:#f85149">total_records</span> <span style="color:#f85149">%</span> <span style="color:#f85149">insert_batch_size</span> <span style="color:#f85149">==</span> <span style="color:#a5d6ff">0</span><span style="color:#f85149">:</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">operations</span> <span style="color:#f85149">=</span> [
</span></span><span style="display:flex;"><span>                <span style="color:#f85149">UpdateOne(</span>
</span></span><span style="display:flex;"><span>                    {<span style="color:#7ee787">&#34;product_name&#34;</span>: <span style="color:#f85149">p_name</span>},
</span></span><span style="display:flex;"><span>                    {<span style="color:#7ee787">&#34;$push&#34;</span>: {<span style="color:#7ee787">&#34;stats&#34;</span>: {<span style="color:#7ee787">&#34;$each&#34;</span>: <span style="color:#f85149">stats_array</span>}}},
</span></span><span style="display:flex;"><span>                    <span style="color:#f85149">upsert=True</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f85149">)</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f85149">for</span> <span style="color:#f85149">p_name</span>, <span style="color:#f85149">stats_array</span> <span style="color:#f85149">in</span> <span style="color:#f85149">product_dict.items()</span>
</span></span><span style="display:flex;"><span>            ]
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">db.product_stats.bulk_write(operations)</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">product_dict.clear()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">寫入剩餘的數據</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">if</span> <span style="color:#f85149">product_dict:</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">operations</span> <span style="color:#f85149">=</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">UpdateOne(</span>
</span></span><span style="display:flex;"><span>            {<span style="color:#7ee787">&#34;product_name&#34;</span>: <span style="color:#f85149">p_name</span>},
</span></span><span style="display:flex;"><span>            {<span style="color:#7ee787">&#34;$push&#34;</span>: {<span style="color:#7ee787">&#34;stats&#34;</span>: {<span style="color:#7ee787">&#34;$each&#34;</span>: <span style="color:#f85149">stats_array</span>}}},
</span></span><span style="display:flex;"><span>            <span style="color:#f85149">upsert=True</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">for</span> <span style="color:#f85149">p_name</span>, <span style="color:#f85149">stats_array</span> <span style="color:#f85149">in</span> <span style="color:#f85149">product_dict.items()</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">db.product_stats.bulk_write(operations)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">計時結束</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">insert_end_time</span> <span style="color:#f85149">=</span> <span style="color:#f85149">time.time()</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">查詢性能測試</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">query_times</span> <span style="color:#f85149">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#f85149">for</span> <span style="color:#f85149">_</span> <span style="color:#f85149">in</span> <span style="color:#f85149">range(</span><span style="color:#a5d6ff">100</span><span style="color:#f85149">):</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">#</span> <span style="color:#f85149">隨機生成查詢的時間區間</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">start_timestamp</span> <span style="color:#f85149">=</span> <span style="color:#f85149">start_time</span> <span style="color:#f85149">+</span> <span style="color:#f85149">timedelta(minutes=random.randint(</span><span style="color:#a5d6ff">0</span><span style="color:#f85149">,</span> <span style="color:#f85149">time_range</span><span style="color:#a5d6ff">-30</span><span style="color:#f85149">))</span>  <span style="color:#f85149">#</span> <span style="color:#f85149">最大值為</span> <span style="color:#f85149">-</span> <span style="color:#a5d6ff">30</span> <span style="color:#f85149">為</span> <span style="color:#a5d6ff">30</span> <span style="color:#f85149">分鐘</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">end_timestamp</span> <span style="color:#f85149">=</span> <span style="color:#f85149">start_timestamp</span> <span style="color:#f85149">+</span> <span style="color:#f85149">timedelta(minutes=</span><span style="color:#a5d6ff">30</span><span style="color:#f85149">)</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">product_names</span> <span style="color:#f85149">=</span> [<span style="color:#f85149">f</span><span style="color:#a5d6ff">&#34;商品名稱{random.randint(1, product_size)}&#34;</span> <span style="color:#f85149">for</span> <span style="color:#f85149">_</span> <span style="color:#f85149">in</span> <span style="color:#f85149">range(</span><span style="color:#a5d6ff">10</span><span style="color:#f85149">)</span>]  <span style="color:#f85149">#</span> <span style="color:#f85149">隨機查詢</span> <span style="color:#a5d6ff">10</span> <span style="color:#f85149">個商品</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">aggregate_query</span> <span style="color:#f85149">=</span> [
</span></span><span style="display:flex;"><span>        {<span style="color:#7ee787">&#34;$match&#34;</span>: {<span style="color:#7ee787">&#34;product_name&#34;</span>: {<span style="color:#7ee787">&#34;$in&#34;</span>: <span style="color:#f85149">product_names</span>}}},
</span></span><span style="display:flex;"><span>        {<span style="color:#7ee787">&#34;$unwind&#34;</span>: <span style="color:#a5d6ff">&#34;$stats&#34;</span>},
</span></span><span style="display:flex;"><span>        {<span style="color:#7ee787">&#34;$match&#34;</span>: {<span style="color:#7ee787">&#34;stats.timestamp&#34;</span>: {<span style="color:#7ee787">&#34;$gt&#34;</span>: <span style="color:#f85149">start_timestamp</span>, <span style="color:#7ee787">&#34;$lte&#34;</span>: <span style="color:#f85149">end_timestamp</span>}}},
</span></span><span style="display:flex;"><span>        {<span style="color:#7ee787">&#34;$project&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;_id&#34;</span>: <span style="color:#a5d6ff">0</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;product_name&#34;</span>: <span style="color:#a5d6ff">1</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;timestamp&#34;</span>: <span style="color:#a5d6ff">&#34;$stats.timestamp&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#7ee787">&#34;data&#34;</span>: <span style="color:#a5d6ff">&#34;$stats.data&#34;</span>
</span></span><span style="display:flex;"><span>        }}
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">#</span> <span style="color:#f85149">執行查詢</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">start_query</span> <span style="color:#f85149">=</span> <span style="color:#f85149">time.time()</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">result</span> <span style="color:#f85149">=</span> <span style="color:#f85149">db.product_stats.aggregate(aggregate_query)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">end_query</span> <span style="color:#f85149">=</span> <span style="color:#f85149">time.time()</span>
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">query_times.append(end_query</span> <span style="color:#f85149">-</span> <span style="color:#f85149">start_query)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">print(f</span><span style="color:#a5d6ff">&#34;MongoDB 寫入時間: {insert_end_time - insert_start_time} 秒&#34;</span><span style="color:#f85149">)</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">print(f</span><span style="color:#a5d6ff">&#34;MongoDB 寫入總量: {total_records} 條&#34;</span><span style="color:#f85149">)</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">#</span> <span style="color:#f85149">計算平均查詢時間</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">average_query_time</span> <span style="color:#f85149">=</span> <span style="color:#f85149">sum(query_times)</span> <span style="color:#f85149">/</span> <span style="color:#f85149">len(query_times)</span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">print(f</span><span style="color:#a5d6ff">&#34;MongoDB 平均查詢時間: {average_query_time} 秒&#34;</span><span style="color:#f85149">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f85149">client.close()</span>
</span></span></code></pre></div><p>下面為 MongoDB 測試方案 3 的結果:</p>
<ul>
<li><strong>寫入時間</strong>: 166.6052 秒</li>
<li><strong>平均查詢時間</strong>: 0.0032 秒</li>
<li><strong>資料大小</strong>: 53.1 MB</li>
</ul>
<hr>
<h2 id="-結論想象跟實際的落差">結論：想象跟實際的落差<a class="h-anchor" href="#-%e7%b5%90%e8%ab%96%e6%83%b3%e8%b1%a1%e8%b7%9f%e5%af%a6%e9%9a%9b%e7%9a%84%e8%90%bd%e5%b7%ae" aria-label="這個段落的連結"></a></h2>
<p>下面我先用 Table 的方式，向大家展示這四個方案的測試結果:</p>
<p><img src="https://deanlin.net/blog/202405/mongodb-vs-mysql-index-performance/images/mongodb-vs-mysql-index-performance-02.webp" width="1028" height="284" alt="結論：想象跟實際的落差（圖 2）" loading="lazy" decoding="async"></p>
<p>從寫入與讀取效能的角度來說，MongoDB 單筆 index 的方案就夠了；但如果想要壓縮資料大小，可以考慮 Time Series Collection 的方案。</p>
<p>至於上一篇文章原本打算使用的 Embedded Documents 方案，在寫入效能方面真的不太好，商品種類越多效能越差，真的是沒實驗過不知道。</p>
<p>至於 MySQL 就是一個安全牌，除非有很極致的需求，不然這個方案沒什麼大缺點。</p>
<p>下面整理一下我這次的實驗心得吧:</p>
<ul>
<li>相同資料庫，在資料結構不同的狀況下，效能會有巨大差異。</li>
<li>如果只看搜尋效率，MongoDB 大多數情境下會贏</li>
<li>其實 MongoDB 跟 MySQL 都能處理這個案例，就看實際業務更關注哪塊的效能。</li>
</ul>
<p>當然小弟的實驗可能有許多考量不足的地方，歡迎各位大神留言交流，謝謝。</p>
<a class="linkcard" href="https://deanlin.net/tags/database/">
  <span class="lc-body">
    <span class="lc-title">Database</span>
    <span class="lc-desc">Edit description</span>
    <span class="lc-site"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M5 12h14M13 6l6 6-6 6"/></svg>林鼎淵 Dean Lin</span>
  </span>
</a>

]]></content:encoded>
    </item>
    <item>
      <title>放完假後 MySQL Server 就掛了，不是每個錯誤都能靠 Google 找到解答</title>
      <link>https://deanlin.net/blog/202206/mysql-server-down-after-holiday/</link>
      <pubDate>Tue, 21 Jun 2022 21:33:28 +0800</pubDate>
      <guid>https://deanlin.net/blog/202206/mysql-server-down-after-holiday/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>如果你用上圖的錯誤訊息去 Google 搜尋，相信會得到很多解答；但這篇文章遇到的問題，如果用關鍵字搜尋是找不到真正解答的；也因為這次的經驗讓我對 Ubuntu Server、MySQL、apt-mark 有更深一層的認識。</description>
      <media:content url="https://deanlin.net/blog/202206/mysql-server-down-after-holiday/images/mysql-server-down-after-holiday-og_hu_941c008b1b9af0f6.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p><img src="https://deanlin.net/blog/202206/mysql-server-down-after-holiday/images/mysql-server-down-after-holiday-01.webp" width="1060" height="561" alt="放完假後 MySQL Server 就掛了，不是每個錯誤都能靠 Google 找到解答（圖 1）" fetchpriority="high" decoding="async"></p>
<p>如果你用上圖的錯誤訊息去 Google 搜尋，相信會得到很多解答；但這篇文章遇到的問題，如果用關鍵字搜尋是找不到真正解答的；也因為這次的經驗讓我對 Ubuntu Server、MySQL、apt-mark 有更深一層的認識。</p>
<pre tabindex="0"><code>大綱
ㄧ、問題描述
二、關鍵字搜尋到的方案
三、從 Error Log 猜想問題
四、問題的真實原因
五、解決方案
六、後記
</code></pre><hr>
<h2 id="ㄧ問題描述">ㄧ、問題描述<a class="h-anchor" href="#%e3%84%a7%e5%95%8f%e9%a1%8c%e6%8f%8f%e8%bf%b0" aria-label="這個段落的連結"></a></h2>
<p>上週建立的中介資料庫在經歷一個假日後，在禮拜一被同事回報登入失敗。</p>
<p>一開始我以為是帳號密碼錯誤，又或是公司網路問題；但在成功登入 Server 後，我發現連 root 的權限都無法登入 MySQL DB，並出現如下錯誤：</p>
<pre tabindex="0"><code>ERROR 2002 (HY000): Can’t connect to local MySQL server through socket &#39;/var/run/mysqld/mysqld.sock&#39; (2)
</code></pre><hr>
<h2 id="二關鍵字搜尋到的方案">二、關鍵字搜尋到的方案<a class="h-anchor" href="#%e4%ba%8c%e9%97%9c%e9%8d%b5%e5%ad%97%e6%90%9c%e5%b0%8b%e5%88%b0%e7%9a%84%e6%96%b9%e6%a1%88" aria-label="這個段落的連結"></a></h2>
<p>遇事不知求 Google，我馬上搜尋過去前輩們是怎麼解決這個問題的。</p>
<p><img src="https://deanlin.net/blog/202206/mysql-server-down-after-holiday/images/mysql-server-down-after-holiday-02.webp" width="1400" height="968" alt="二、關鍵字搜尋到的方案（圖 2）" loading="lazy" decoding="async"></p>
<p>解決的方法五花八門，我這邊統整一下大致的方向：</p>
<ol>
<li>重啟 MySQL 服務</li>
<li>調整 MySQL 資料夾權限，補齊遺漏的檔案</li>
<li>重啟整台 Server</li>
<li>把 MySQL 砍掉重裝</li>
</ol>
<p>但很遺憾的，1~3 我都嘗試過了，MySQL 依舊頑強的吐一樣的錯誤，看來不能依靠網路的的解法了。</p>
<hr>
<h2 id="三從-error-log-猜想問題">三、從 Error Log 猜想問題<a class="h-anchor" href="#%e4%b8%89%e5%be%9e-error-log-%e7%8c%9c%e6%83%b3%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h2>
<p>因為網路上的方案都失效，所以決定換一個問題處理的方向，去找 MySQL 的 Error Log（其實一開始應該就要這樣做）。</p>
<p><strong>STEP 1</strong>：前往 MySQL 的設定檔。</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
</span></span></code></pre></div><p><strong>STEP 2</strong>：查看 Error Log 存放路徑。</p>
<p><img src="https://deanlin.net/blog/202206/mysql-server-down-after-holiday/images/mysql-server-down-after-holiday-03.webp" width="1400" height="497" alt="三、從 Error Log 猜想問題（圖 3）" loading="lazy" decoding="async"></p>
<p><strong>STEP 3</strong>：查看 Error Log 內容。</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo vim /var/log/mysql/error.log
</span></span></code></pre></div><p><img src="https://deanlin.net/blog/202206/mysql-server-down-after-holiday/images/mysql-server-down-after-holiday-04.webp" width="1400" height="599" alt="三、從 Error Log 猜想問題（圖 4）" loading="lazy" decoding="async"></p>
<p>從時間軸發現服務被完整的關閉後就無法重啟，但假日有誰會把服務給關閉呢？</p>
<p>於是我便詢問公司的 MIS 部門，在假日是否有對 Server 進行維護作業。</p>
<p>果然如我所料，對方表示在假日有對各 Server 系統做例行性維護升級，而這台 Ubuntu Server 的 MySQL 正是在這次升級後出問題。</p>
<hr>
<h2 id="四問題的真實原因">四、問題的真實原因<a class="h-anchor" href="#%e5%9b%9b%e5%95%8f%e9%a1%8c%e7%9a%84%e7%9c%9f%e5%af%a6%e5%8e%9f%e5%9b%a0" aria-label="這個段落的連結"></a></h2>
<p>既然問題是由系統升級引起的，那就比較有解決的方向了；在與 MIS 同仁討論後，我們猜想問題應該是 Ubuntu Server 升級後內建的 MySQL 版本為 8.0，但原本安裝的版本為 5.7，因此在升級後發生版本對應不到的問題。</p>
<p>於是我們便執行下面的指令來查看目前 apt 安裝的套件有哪些，以及對應的版本：</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-bash" data-lang="bash"><span style="display:flex;"><span>apt list --installed
</span></span></code></pre></div><p>結果如我們所想，系統更新後，新版本的 MySQL 真的把舊版本給覆蓋住了。</p>
<hr>
<h2 id="五解決方案">五、解決方案<a class="h-anchor" href="#%e4%ba%94%e8%a7%a3%e6%b1%ba%e6%96%b9%e6%a1%88" aria-label="這個段落的連結"></a></h2>
<p>但因為這台是給其他合作單位使用的資料庫，而對方因為總總歷史包袱只能選用 MySQL 5.7，所以我們是不能對它做升級的。</p>
<p>但幸好 MIS 在維護前有先將 Server 備份，因此我們如下步驟進行處理：</p>
<p><strong>STEP 1</strong>：用備份將原本的 Ubuntu Server 還原。</p>
<p><strong>STEP 2：</strong>下指令將 MySQL Server 版本鎖定。</p>
<pre tabindex="0"><code>apt-mark hold mysql-server
</code></pre><p><strong>STEP 3</strong>：查看是否有鎖定成功。</p>
<pre tabindex="0"><code>apt-mark showhold
</code></pre><p><strong>STEP 4：</strong>鎖定後再將 Ubuntu Server 做升級維護。</p>
<p><strong>STEP 5：</strong>升級完成後確認 MySQL 版本有鎖定住。</p>
<pre tabindex="0"><code>mysql --version
</code></pre><p>透過上面的步驟後，MySQL 就成功復活了～</p>
<hr>
<h2 id="六後記">六、後記<a class="h-anchor" href="#%e5%85%ad%e5%be%8c%e8%a8%98" aria-label="這個段落的連結"></a></h2>
<p>這個問題花了不少時間才解決，主要是因為我在一開始判斷問題時就走偏了；過去我都是從錯誤訊息找答案，而這次的錯誤訊息卻把我引導到另一個錯誤的方向。</p>
<p>這次的經驗也讓我明白，除了發生問題的本體外，還要多觀察其他有「相依性」的資訊，否則會陷入死胡同，還期待用錯誤的方法得到正確的結果。</p>
<p>但同時也提醒大家，千萬不要看完這篇文章後，回去就把所有安裝的套件版本都鎖死；除非有不得已的理由，不然系統、套件都是需要定期更新的，這樣穩定性、安全性才足夠。</p>
]]></content:encoded>
    </item>
    <item>
      <title>初探 sequelize，在 Node.js 快速建立 Migration、Seeder（ MySQL）</title>
      <link>https://deanlin.net/blog/202205/sequelize-migration-seeder/</link>
      <pubDate>Fri, 13 May 2022 15:14:28 +0800</pubDate>
      <guid>https://deanlin.net/blog/202205/sequelize-migration-seeder/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>最近在尋找 Node.js 裡面方便操作 Migration、Seeder 的套件，逛著逛著發現 sequelize 在 GitHub 上擁有超高人氣；抱著踩雷的心態來嘗試，於是便寫了這篇 sequelize 的筆記，希望可以幫助到第一次使用 sequelize 的朋友們。</description>
      <media:content url="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-og_hu_b8d9851da479dae8.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>最近在尋找 Node.js 裡面方便操作 Migration、Seeder 的套件，逛著逛著發現 sequelize 在 GitHub 上擁有超高人氣；抱著踩雷的心態來嘗試，於是便寫了這篇 sequelize 的筆記，希望可以幫助到第一次使用 sequelize 的朋友們。</p>
<ul>
<li><strong>我能學到什麼</strong> 🤔：sequelize 基礎使用方式。</li>
<li><strong>我能學會嗎</strong> 🤔：筆者從零開始建立專案，並附上詳細的指令與說明，相信大家都能學得開心。</li>
<li><strong>文章目標</strong> 🤩：建立一個有使用者（user）、訂單（order）、購買貨物（item）的資料庫，設計 Table 彼此的關連性，透過 Query 確認資料。</li>
</ul>
<p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-01.webp" width="1238" height="658" alt="初探 sequelize，在 Node.js 快速建立 Migration、Seeder（ MySQL）（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>大綱
一、初始化專案
二、透過 Config 建立 DB
三、用 Migration 建立 Table
四、用 Seeder 建立假資料(Dummy Data)
五、簡易 Query
</code></pre><blockquote>
<p>大家也可以<a href="https://github.com/dean9703111/sequelize-mysql-migration-seeder">直接 Clone 筆者 GitHub 上的專案</a>來了解喔。（如果順便按個 Star更好😀）</p>
</blockquote>
<h2 id="ㄧ初始化專案">ㄧ、初始化專案<a class="h-anchor" href="#%e3%84%a7%e5%88%9d%e5%a7%8b%e5%8c%96%e5%b0%88%e6%a1%88" aria-label="這個段落的連結"></a></h2>
<p><strong>STEP 1</strong>：建立專案資料夾</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-bash" data-lang="bash"><span style="display:flex;"><span>mkdir sequelize-demo <span style="color:#ff7b72;font-weight:bold">&amp;&amp;</span> cd  sequelize-demo
</span></span><span style="display:flex;"><span>npm init
</span></span></code></pre></div><p><strong>STEP 2</strong>：安裝 sequelize</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-bash" data-lang="bash"><span style="display:flex;"><span>npm install -g sequelize-cli
</span></span><span style="display:flex;"><span>npm install --save sequelize
</span></span></code></pre></div><p><strong>STEP 3</strong>：安裝你想使用的 DB（本篇文章以 MySQL 為範例）</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-bash" data-lang="bash"><span style="display:flex;"><span>npm install --save mysql2 <span style="color:#8b949e;font-style:italic">#本篇文章</span>
</span></span><span style="display:flex;"><span>npm install --save pg pg-hstore <span style="color:#8b949e;font-style:italic"># Postgres</span>
</span></span><span style="display:flex;"><span>npm install --save mariadb
</span></span><span style="display:flex;"><span>npm install --save sqlite3
</span></span><span style="display:flex;"><span>npm install --save tedious <span style="color:#8b949e;font-style:italic"># Microsoft SQL Server</span>
</span></span></code></pre></div><hr>
<h2 id="二透過-config-建立-db">二、透過 Config 建立 DB<a class="h-anchor" href="#%e4%ba%8c%e9%80%8f%e9%81%8e-config-%e5%bb%ba%e7%ab%8b-db" aria-label="這個段落的連結"></a></h2>
<p><strong>STEP 1</strong>：用指令初始化相關設定檔</p>
<pre tabindex="0"><code>sequelize init
</code></pre><p><strong>STEP 2</strong>：調整 sequelize 的 <code>config/config.json</code> 檔</p>
<p>預設使用的環境都是<code>development</code>，記得調整成自己的 DB 登入參數</p>
<pre tabindex="0"><code>&#34;development&#34;: {
    &#34;username&#34;: &#34;root&#34;,
    &#34;password&#34;: &#34;baobaoverycute&#34;,
    &#34;database&#34;: &#34;sequelize_demo&#34;,
    &#34;host&#34;: &#34;127.0.0.1&#34;,
    &#34;dialect&#34;: &#34;mysql&#34;
  },
</code></pre><p><strong>STEP 3</strong>：依照剛剛設定的 <code>config/config.json</code> 來建立 DB</p>
<pre tabindex="0"><code>sequelize db:create
</code></pre><p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-02.webp" width="1400" height="299" alt="二、透過 Config 建立 DB（圖 2）" loading="lazy" decoding="async"></p>
<blockquote>
<p>如果想要移除該 DB，可透過指令： <code>sequelize db:drop</code></p>
</blockquote>
<hr>
<h2 id="三用-migration-建立-table">三、用 Migration 建立 Table<a class="h-anchor" href="#%e4%b8%89%e7%94%a8-migration-%e5%bb%ba%e7%ab%8b-table" aria-label="這個段落的連結"></a></h2>
<p><strong>STEP 1</strong>：新增所需 Tables &amp; 基礎欄位</p>
<p>根據文章目標，我們需要建立 3 個 Tables：使用者（user）、訂單（order）、購買貨物（item）</p>
<pre tabindex="0"><code>sequelize model:generate --name user --attributes name:string,mail:string
sequelize model:generate --name order --attributes user_id:integer,total_price:integer
sequelize model:generate --name item --attributes order_id:integer,price:integer
</code></pre><blockquote>
<p>如果沒有填寫「attributes」的參數會無法建立 Table。</p>
</blockquote>
<p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-03.webp" width="1400" height="318" alt="三、用 Migration 建立 Table（圖 3）" loading="lazy" decoding="async"></p>
<p><strong>STEP 2</strong>：前往 migrations 的資料夾，設計 Table 間的關聯性</p>
<p>調整 order Table，建立 user_id 與 user 的關聯性（一個 user 會對應到多筆 order）</p>
<pre tabindex="0"><code>user_id: {
    type: Sequelize.INTEGER,
    references: {
        model: &#39;users&#39;,
        key: &#39;id&#39;
    },
},
</code></pre><p>調整 item Table，建立 order_id 與 item 的關聯性（一個 order 會對應到多筆 item）</p>
<pre tabindex="0"><code>oder_id: {
    type: Sequelize.INTEGER,
    references: {
        model: &#39;orders&#39;,
        key: &#39;id&#39;
    },
},
</code></pre><p><strong>STEP 3</strong>：修改完成之後，就可以下指令建立剛剛設定的 Table 啦～</p>
<pre tabindex="0"><code>sequelize db:migrate
</code></pre><p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-04.webp" width="1400" height="546" alt="三、用 Migration 建立 Table（圖 4）" loading="lazy" decoding="async"></p>
<p>下圖是用 MySQLWorkbench 產生的 ER Diagram</p>
<p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-05.webp" width="1400" height="1022" alt="三、用 Migration 建立 Table（圖 5）" loading="lazy" decoding="async"></p>
<p>如果想要回溯 migration 可用如下指令：</p>
<ul>
<li>退一個版本：<code>sequelize db:migrate:undo</code></li>
<li>退到初始狀態：<code>sequelize db:migrate:undo:all</code></li>
<li>退到指定版本：<code>sequelize db:migrate:undo:all &ndash;to XXXXXXXXXXXXXX-create-user.js</code></li>
</ul>
<hr>
<h2 id="四用-seeder-建立假資料dummy-data">四、用 Seeder 建立假資料(Dummy Data)<a class="h-anchor" href="#%e5%9b%9b%e7%94%a8-seeder-%e5%bb%ba%e7%ab%8b%e5%81%87%e8%b3%87%e6%96%99dummy-data" aria-label="這個段落的連結"></a></h2>
<p><strong>目標</strong>：建立 100 個使用者（user），每個使用者有 10 筆訂單（order），每筆訂單下面有 10 個貨物（item）。</p>
<p><strong>STEP 1</strong>：先建立 Seeder 檔案</p>
<pre tabindex="0"><code>sequelize seed:generate --name demo-user
sequelize seed:generate --name demo-order
sequelize seed:generate --name demo-item
</code></pre><p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-06.webp" width="1400" height="313" alt="四、用 Seeder 建立假資料(Dummy Data)（圖 6）" loading="lazy" decoding="async"></p>
<p><strong>STEP 2</strong>：建立各自 Seeder 資料產生的邏輯，下面以 order 舉例：</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-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a5d6ff">&#34;use strict&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>module.exports <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">async</span> up(queryInterface, Sequelize) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">function</span> getRandomInt(max) {
</span></span><span style="display:flex;"><span>      <span style="color:#ff7b72">return</span> Math.floor(Math.random() <span style="color:#ff7b72;font-weight:bold">*</span> max);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">// 100 個使用者，每個使用者有 10 筆訂單（order）
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">var</span> orderArray <span style="color:#ff7b72;font-weight:bold">=</span> [];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">let</span> i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>; i <span style="color:#ff7b72;font-weight:bold">&lt;=</span> <span style="color:#a5d6ff">100</span>; i<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#ff7b72">for</span> (<span style="color:#ff7b72">let</span> j <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>; j <span style="color:#ff7b72;font-weight:bold">&lt;=</span> <span style="color:#a5d6ff">10</span>; j<span style="color:#ff7b72;font-weight:bold">++</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">const</span> order <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>          user_id<span style="color:#ff7b72;font-weight:bold">:</span> i,
</span></span><span style="display:flex;"><span>          total_price<span style="color:#ff7b72;font-weight:bold">:</span> Math.floor(Math.random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">1000</span>),
</span></span><span style="display:flex;"><span>          createdAt<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#ff7b72">new</span> Date(),
</span></span><span style="display:flex;"><span>          updatedAt<span style="color:#ff7b72;font-weight:bold">:</span> <span style="color:#ff7b72">new</span> Date(),
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>        orderArray.push(order);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> queryInterface.bulkInsert(<span style="color:#a5d6ff">&#34;Orders&#34;</span>, orderArray);
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">async</span> down(queryInterface, Sequelize) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> queryInterface.bulkDelete(<span style="color:#a5d6ff">&#34;Orders&#34;</span>, <span style="color:#79c0ff">null</span>, {});
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/1c59dbb3e507a3de6c6bfab4e3ead327" rel="noopener">sequelize-order.js</a>（GitHub Gist）</p>
<p><strong>STEP 3</strong>：執行 Seeder</p>
<p>執行單個 Seeder</p>
<pre tabindex="0"><code>sequelize db:seed --seed my_seeder_file.js
</code></pre><p>執行全部 Seeder</p>
<pre tabindex="0"><code>sequelize db:seed:all
</code></pre><p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-07.webp" width="1400" height="560" alt="四、用 Seeder 建立假資料(Dummy Data)（圖 7）" loading="lazy" decoding="async"></p>
<p><strong>STEP 4</strong>：前往 DB ，確認資料是否有正確寫入：</p>
<p>建立 100 個使用者（users）。</p>
<p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-08.webp" width="1308" height="658" alt="四、用 Seeder 建立假資料(Dummy Data)（圖 8）" loading="lazy" decoding="async"></p>
<p>建立 100 個使用者（users），每個使用者有 10 筆訂單（orders），共 1000 筆資料。</p>
<p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-09.webp" width="1308" height="658" alt="四、用 Seeder 建立假資料(Dummy Data)（圖 9）" loading="lazy" decoding="async"></p>
<p>建立 100 個使用者（users），每個使用者有 10 筆訂單（orders），每筆訂單下面有 10 個貨物（items），共 10000 筆資料。</p>
<p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-10.webp" width="1308" height="700" alt="四、用 Seeder 建立假資料(Dummy Data)（圖 10）" loading="lazy" decoding="async"></p>
<p>取消執行的 Seeder 可用如下指令：</p>
<ul>
<li>取消最近的 Seeder：<code>sequelize db:seed:undo</code></li>
<li>取消指定的 Seeder：<code>sequelize db:seed:undo &ndash;seed my_seeder_file.js</code></li>
<li>取消全部 Seeder：<code>sequelize db:seed:undo:all</code></li>
</ul>
<hr>
<h2 id="五簡易-query">五、簡易 Query<a class="h-anchor" href="#%e4%ba%94%e7%b0%a1%e6%98%93-query" aria-label="這個段落的連結"></a></h2>
<p><strong>STEP 1</strong>：在專案資料夾下建立一個 <code>row-queries-exmaple.js</code> 的檔案。</p>
<p><strong>STEP 2</strong>：將如下程式複製貼上，這是一個簡單 Raw Queries；會找出 price 低於 50 的 5 筆購買貨物，以及對應的使用者。</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-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { sequelize } <span style="color:#ff7b72;font-weight:bold">=</span> require(<span style="color:#a5d6ff">&#34;./models&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">const</span> { QueryTypes } <span style="color:#ff7b72;font-weight:bold">=</span> sequelize;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">async</span> <span style="color:#ff7b72">function</span> rawQueries() {
</span></span><span style="display:flex;"><span>  console.time(<span style="color:#a5d6ff">&#34;rawQueries&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> results <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">await</span> sequelize.query(<span style="color:#a5d6ff">&#34;SELECT users.name, items.price FROM users\
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">         LEFT JOIN orders ON orders.user_id = users.id\
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">         LEFT JOIN items ON items.order_id = orders.id\
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">         WHERE items.price &lt; 50 LIMIT 5&#34;</span>, { type<span style="color:#ff7b72;font-weight:bold">:</span> QueryTypes.SELECT });
</span></span><span style="display:flex;"><span>  console.log(JSON.stringify(results, <span style="color:#79c0ff">null</span>, <span style="color:#a5d6ff">2</span>));
</span></span><span style="display:flex;"><span>  console.timeEnd(<span style="color:#a5d6ff">&#34;rawQueries&#34;</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>rawQueries();
</span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/78f3d7405983a6f5888550b67bf8dbee" rel="noopener">sequelize-raw-queries.js</a>（GitHub Gist）</p>
<p><strong>STEP 3</strong>：在終端機輸入：<code>node row-queries-exmaple.js</code></p>
<p>下圖即可看到看到搜尋結果、程式執行時間。</p>
<p><img src="https://deanlin.net/blog/202205/sequelize-migration-seeder/images/sequelize-migration-seeder-11.webp" width="1400" height="962" alt="五、簡易 Query（圖 11）" loading="lazy" decoding="async"></p>
<blockquote class="pullquote"><p>如果你想要知道 Table Join 時，選擇文字欄位（VARCHAR）與數值欄位（INTEGER）在效能（performance）上的差異，<a href="https://deanlin.net/blog/202205/join-performance-varchar-vs-integer/">可以參考這篇文章</a>。</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>解決 MySQL 主從複製的延遲問題 (Replication lag)</title>
      <link>https://deanlin.net/blog/202103/mysql-replication-lag/</link>
      <pubDate>Mon, 29 Mar 2021 15:27:42 +0800</pubDate>
      <guid>https://deanlin.net/blog/202103/mysql-replication-lag/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>Slave DB 上的資料從 Master DB 複製過來時會有延遲，所以會造成用戶看到舊的資訊；在過去想解決這個問題真的需要 18 般武藝，但到了 MySQL 5.7 版後官方說只要調整一個設定就能解決，具體做法讓我們看下去。</description>
      <media:content url="https://deanlin.net/blog/202103/mysql-replication-lag/images/mysql-replication-lag-og_hu_e6c9419343a55c95.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>在『MySQL Master Slave Replication 主從式架構設定教學』這篇文章的結尾有提到因為 Slave DB 上的資料從 Master DB 複製過來時會有延遲，所以會造成用戶看到舊的資訊；在過去想解決這個問題真的需要 18 般武藝，但到了 MySQL 5.7 版後官方說只要調整一個設定就能解決，具體做法讓我們看下去。</p>
<p><img src="https://deanlin.net/blog/202103/mysql-replication-lag/images/mysql-replication-lag-01.webp" width="1400" height="783" alt="解決 MySQL 主從複製的延遲問題 (Replication lag)（圖 1）" fetchpriority="high" decoding="async"></p>
<pre tabindex="0"><code>一、模擬主從複製延遲的情境
二、了解發生問題的原因
三、解決方案：開啟 MySQL 5.7 的多 process 的功能
四、確認 Slave 已經沒有延遲
</code></pre><h2 id="一模擬主從複製延遲的情境">一、模擬主從複製延遲的情境<a class="h-anchor" href="#%e4%b8%80%e6%a8%a1%e6%93%ac%e4%b8%bb%e5%be%9e%e8%a4%87%e8%a3%bd%e5%bb%b6%e9%81%b2%e7%9a%84%e6%83%85%e5%a2%83" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>這裡我們利用 sysbench 進行情境模擬，如果不清楚如何操作的朋友請參考這篇文章：『<a href="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/">用 sysbench 測試 MySQL 性能 — 在 mac &amp; ubuntu 上的安裝與使用</a>』</p>
</blockquote>
<p>下面是我在 ubuntu 的測試指令，如果你是 mac 需要自己更換 .lua 的路徑</p>
<pre tabindex="0"><code>sysbench /usr/share/sysbench/oltp_write_only.lua \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=user_name \
--mysql-password=user_password \
--mysql-db=test \
--db-driver=mysql \
--tables=10 \
--table_size=100000 \
--report-interval=10 \
--threads=128 \
--time=120 \
prepare
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/46688419066279598ee33553f5ae9c45" rel="noopener">sysbench_lag.cmd</a>（GitHub Gist）</p>
<p>指令輸入後你就會發現資料庫好像頓住不會動了…</p>
<p><img src="https://deanlin.net/blog/202103/mysql-replication-lag/images/mysql-replication-lag-02.webp" width="916" height="912" alt="一、模擬主從複製延遲的情境（圖 2）" loading="lazy" decoding="async"></p>
<p>一段時間後我們進入到 Slave 的 MySQL 中會發現他與 Master 的延遲很嚴重</p>
<pre tabindex="0"><code>mysql&gt; show slave status\G
</code></pre><ul>
<li><strong>Seconds_Behind_Master</strong>：延遲的時間</li>
</ul>
<p><img src="https://deanlin.net/blog/202103/mysql-replication-lag/images/mysql-replication-lag-03.webp" width="874" height="236" alt="一、模擬主從複製延遲的情境（圖 3）" loading="lazy" decoding="async"></p>
<h2 id="二了解發生問題的原因">二、了解發生問題的原因<a class="h-anchor" href="#%e4%ba%8c%e4%ba%86%e8%a7%a3%e7%99%bc%e7%94%9f%e5%95%8f%e9%a1%8c%e7%9a%84%e5%8e%9f%e5%9b%a0" aria-label="這個段落的連結"></a></h2>
<p>因為我們模擬的情境會製作出相當大量的 SQL command，執行內容包含：table 內容大量更新、index 建立；可是 Slave 去讀取 Master 的 process 卻只有一個，這樣使 Master 累積了太多尚未同步到 Slave 的 SQL command，因此造成了兩邊資料庫的內容不一致。</p>
<h2 id="三解決方案開啟-mysql-57-的多-process-的功能">三、解決方案：開啟 MySQL 5.7 的多 process 的功能<a class="h-anchor" href="#%e4%b8%89%e8%a7%a3%e6%b1%ba%e6%96%b9%e6%a1%88%e9%96%8b%e5%95%9f-mysql-57-%e7%9a%84%e5%a4%9a-process-%e7%9a%84%e5%8a%9f%e8%83%bd" aria-label="這個段落的連結"></a></h2>
<p><strong>STEP 1</strong>：先暫停 Slave，不然無法修改變數</p>
<pre tabindex="0"><code>mysql&gt; stop slave;
</code></pre><p><strong>STEP 2</strong>：更改 slave_parallel_type</p>
<p>先看看預設值</p>
<pre tabindex="0"><code>mysql&gt; show variables like ‘slave_parallel%’;
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-replication-lag/images/mysql-replication-lag-04.webp" width="403" height="163" alt="三、解決方案：開啟 MySQL 5.7 的多 process 的功能（圖 4）" loading="lazy" decoding="async"></p>
<p>修改變數</p>
<pre tabindex="0"><code>mysql&gt; set global slave_parallel_type=&#39;logical_clock&#39;;
mysql&gt; set global slave_parallel_workers=10; #根據實際需求設定
</code></pre><p>啟動 Slave 功能</p>
<pre tabindex="0"><code>mysql&gt; start slave;
</code></pre><p>查看剛剛設定的多 process 是否生效</p>
<pre tabindex="0"><code>mysql&gt; show processlist;
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-replication-lag/images/mysql-replication-lag-05.webp" width="1206" height="373" alt="三、解決方案：開啟 MySQL 5.7 的多 process 的功能（圖 5）" loading="lazy" decoding="async"></p>
<h2 id="四確認-slave-已經沒有延遲">四、確認 Slave 已經沒有延遲<a class="h-anchor" href="#%e5%9b%9b%e7%a2%ba%e8%aa%8d-slave-%e5%b7%b2%e7%b6%93%e6%b2%92%e6%9c%89%e5%bb%b6%e9%81%b2" aria-label="這個段落的連結"></a></h2>
<p>在上面的設定後稍等一段時間再去查看 Slave 的延遲狀態</p>
<pre tabindex="0"><code>mysql&gt; show slave status\G
</code></pre><p>就發現他已經變成 0 嚕～</p>
<p><img src="https://deanlin.net/blog/202103/mysql-replication-lag/images/mysql-replication-lag-06.webp" width="482" height="129" alt="四、確認 Slave 已經沒有延遲（圖 6）" loading="lazy" decoding="async"></p>
<hr>
<pre tabindex="0"><code>如果你想了解資料庫主從式架構的設定歡迎參考我的文章：
👍 MySQL Master Slave Replication 主從式架構設定教學
👍 Ubuntu 用 Atlas 實現 MySQL Proxy 讀寫分離 read/write splitting
</code></pre><p><strong>參考資訊</strong>：<a href="https://jerryw.blog/2021/01/07/mysql-master-slave-%E8%A4%87%E5%AF%AB%E5%BB%B6%E6%BB%AF%E5%90%8C%E6%AD%A5%E5%95%8F%E9%A1%8C/">mysql master-slave 複寫延滯同步問題</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>用 sysbench 測試 MySQL 性能 — 在 mac &amp; ubuntu 上的安裝與使用</title>
      <link>https://deanlin.net/blog/202103/mysql-benchmark-sysbench/</link>
      <pubDate>Mon, 29 Mar 2021 14:43:32 +0800</pubDate>
      <guid>https://deanlin.net/blog/202103/mysql-benchmark-sysbench/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>sysbench 是我目前使用上最順手的一個測試工具，安裝、使用都非常容易，除了資料庫的測試外他還能對 CPU、Memory…做測試，本篇會詳細說明如何使用它來做 MySQL 性能測試，其他的功能可以參考文末附的連結。</description>
      <media:content url="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-og_hu_19a351296783d12c.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>sysbench 是我目前使用上最順手的一個測試工具，安裝、使用都非常容易，除了資料庫的測試外他還能對 CPU、Memory…做測試，本篇會詳細說明如何使用它來做 MySQL 性能測試，其他的功能可以參考文末附的連結。</p>
<p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-01.webp" width="1400" height="785" alt="用 sysbench 測試 MySQL 性能 — 在 mac &amp; ubuntu 上的安裝與使用（圖 1）" fetchpriority="high" decoding="async"></p>
<h2 id="一在-mac-上操作-sysbench">一、在 Mac 上操作 sysbench<a class="h-anchor" href="#%e4%b8%80%e5%9c%a8-mac-%e4%b8%8a%e6%93%8d%e4%bd%9c-sysbench" aria-label="這個段落的連結"></a></h2>
<h3 id="-安裝-sysbench">安裝 sysbench<a class="h-anchor" href="#-%e5%ae%89%e8%a3%9d-sysbench" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>如果還沒安裝過 brew 請<a href="https://brew.sh/zh-tw/">參考官網</a></p>
</blockquote>
<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-bash" data-lang="bash"><span style="display:flex;"><span>brew install sysbench
</span></span></code></pre></div><p>安裝完成後輸入指令確認安裝完成</p>
<pre tabindex="0"><code>sysbench — version
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-02.webp" width="880" height="92" alt="安裝 sysbench（圖 2）" loading="lazy" decoding="async"></p>
<p>我安裝的版本為 <code>1.0.20</code> 這個會跟下面的指令路徑有關，如果你安裝的是其他版本要自行修改這個版本路徑</p>
<h3 id="-測試-mysql-性能">測試 MySQL 性能<a class="h-anchor" href="#-%e6%b8%ac%e8%a9%a6-mysql-%e6%80%a7%e8%83%bd" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>mac 的範例是本地端的測試，針對 read 這塊</p>
</blockquote>
<p><strong>STEP 1</strong>：先建立好測試用的 DB</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">database</span><span style="color:#6e7681"> </span>test;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><strong>STEP 2</strong>：建立測試資料</p>
<ul>
<li><strong>mysql-user、mysql-password</strong>：改成自己 MySQL 的使用者</li>
<li><strong>mysql-db</strong>：測試的資料庫</li>
<li><strong>range_size、table_size、tables</strong>：測試的規模</li>
<li><strong>prepare</strong>：建立測資的意思</li>
</ul>
<pre tabindex="0"><code>sysbench /usr/local/Cellar/sysbench/1.0.20/share/sysbench/oltp_read_only.lua \
--db-driver=mysql \
--mysql-user=user_name \
--mysql-password=user_password \
--mysql-db=test \
--range_size=100 \
--table_size=10000 \
--tables=2 \
--threads=1 \
--events=0 \
--time=60 \
--rand-type=uniform \
prepare
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/d26df5b62cc8b790085ef4d6ee468883" rel="noopener">sysbench_prepare.cmd</a>（GitHub Gist）</p>
<p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-03.webp" width="1032" height="334" alt="測試 MySQL 性能（圖 3）" loading="lazy" decoding="async"></p>
<p><strong>STEP 3</strong>：進行測試</p>
<blockquote>
<p>只是把指令最後面改成 run</p>
</blockquote>
<pre tabindex="0"><code>sysbench /usr/local/Cellar/sysbench/1.0.20/share/sysbench/oltp_read_only.lua \
--db-driver=mysql \
--mysql-user=user_name \
--mysql-password=user_password \
--mysql-db=test \
--range_size=100 \
--table_size=10000 \
--tables=2 \
--threads=1 \
--events=0 \
--time=60 \
--rand-type=uniform \
run
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/a3093daa95c2b7cae4dd327fcae4d9ba" rel="noopener">sysbench_run.cmd</a>（GitHub Gist）</p>
<p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-04.webp" width="1330" height="1378" alt="測試 MySQL 性能（圖 4）" loading="lazy" decoding="async"></p>
<p><strong>STEP 4</strong>：清除測試數據</p>
<blockquote>
<p>只是把指令最後面改成 cleanup</p>
</blockquote>
<pre tabindex="0"><code>sysbench /usr/local/Cellar/sysbench/1.0.20/share/sysbench/oltp_read_only.lua \
--db-driver=mysql \
--mysql-user=user_name \
--mysql-password=user_password \
--mysql-db=test \
--range_size=100 \
--table_size=10000 \
--tables=2 \
--threads=1 \
--events=0 \
--time=60 \
--rand-type=uniform \
cleanup
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/24b62f7153920bd3fb4773d64f461945" rel="noopener">sysbench_cleanup.cmd</a>（GitHub Gist）</p>
<p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-05.webp" width="992" height="152" alt="測試 MySQL 性能（圖 5）" loading="lazy" decoding="async"></p>
<h2 id="一在-ubuntu-上操作-sysbench">一、在 Ubuntu 上操作 sysbench<a class="h-anchor" href="#%e4%b8%80%e5%9c%a8-ubuntu-%e4%b8%8a%e6%93%8d%e4%bd%9c-sysbench" aria-label="這個段落的連結"></a></h2>
<h3 id="-安裝-sysbench-1">安裝 sysbench<a class="h-anchor" href="#-%e5%ae%89%e8%a3%9d-sysbench-1" aria-label="這個段落的連結"></a></h3>
<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-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt-get install sysbench -y
</span></span></code></pre></div><p>安裝完成後輸入指令確認安裝完成</p>
<pre tabindex="0"><code>sysbench — version
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-06.webp" width="696" height="82" alt="安裝 sysbench（圖 6）" loading="lazy" decoding="async"></p>
<h3 id="-測試-mysql-性能-1">測試 MySQL 性能<a class="h-anchor" href="#-%e6%b8%ac%e8%a9%a6-mysql-%e6%80%a7%e8%83%bd-1" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>ubuntu 的範例是遠端的測試，針對 write這塊</p>
</blockquote>
<p><strong>STEP 1</strong>：先建立好測試用的 DB</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">database</span><span style="color:#6e7681"> </span>test;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><strong>STEP 2</strong>：建立測試資料</p>
<p>如果你的設定跟我的範例一樣可能要稍微等一下，因為範例的測資比較大筆</p>
<ul>
<li><strong>mysql-host</strong>：目標測試的 ip 或是 host name</li>
<li><strong>mysql-port</strong>：目標測試的 MySQL port</li>
<li><strong>mysql-user、mysql-password</strong>：改成自己 MySQL 的使用者</li>
<li><strong>mysql-db</strong>：測試的資料庫</li>
<li><strong>range_size、table_size、tables</strong>：測試的規模</li>
<li><strong>prepare</strong>：建立測資的意思</li>
</ul>
<pre tabindex="0"><code>sysbench /usr/share/sysbench/oltp_write_only.lua \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=user_name \
--mysql-password=user_password \
--mysql-db=test \
--db-driver=mysql \
--tables=10 \
--table_size=10000 \
--report-interval=10 \
--threads=128 \
--time=120 \
prepare
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/4adca32bb1877a41e51c23ff3ff127bf" rel="noopener">ubuntu_sysbench_prepare.cmd</a>（GitHub Gist）</p>
<p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-07.webp" width="1026" height="1340" alt="測試 MySQL 性能（圖 7）" loading="lazy" decoding="async"></p>
<p><strong>STEP 3</strong>：進行測試</p>
<blockquote>
<p>只是把指令最後面改成 run</p>
</blockquote>
<pre tabindex="0"><code>sysbench /usr/share/sysbench/oltp_write_only.lua \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=user_name \
--mysql-password=user_password \
--mysql-db=test \
--db-driver=mysql \
--tables=10 \
--table_size=10000 \
--report-interval=10 \
--threads=128 \
--time=120 \
run
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/24b504d1f2056b6645b63a55ac2856c4" rel="noopener">ubuntu_sysbench_run.cmd</a>（GitHub Gist）</p>
<p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-08.webp" width="1104" height="895" alt="測試 MySQL 性能（圖 8）" loading="lazy" decoding="async"></p>
<p><strong>STEP 4</strong>：清除測試數據</p>
<blockquote>
<p>只是把指令最後面改成 cleanup</p>
</blockquote>
<pre tabindex="0"><code>sysbench /usr/share/sysbench/oltp_write_only.lua \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=user_name \
--mysql-password=user_password \
--mysql-db=test \
--db-driver=mysql \
--tables=10 \
--table_size=10000 \
--report-interval=10 \
--threads=128 \
--time=120 \
cleanup
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/957b162ca5a0bbe57a86e7c792e2a085" rel="noopener">ubuntu_sysbench_cleanup.cmd</a>（GitHub Gist）</p>
<p><img src="https://deanlin.net/blog/202103/mysql-benchmark-sysbench/images/mysql-benchmark-sysbench-09.webp" width="458" height="226" alt="測試 MySQL 性能（圖 9）" loading="lazy" decoding="async"></p>
<hr>
<blockquote class="pullquote"><p>以上是今天的分享，想了解更多功能請參考<a href="https://github.com/akopytov/sysbench">官方的 github</a></p>
</blockquote>
<p><strong>參考資源</strong>：Ubuntu 16.04中進行Sysbench環境搭建、<a href="https://blog.51cto.com/13990437/2400940">sysbench安装和使用</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Ubuntu 用 Atlas 實現 MySQL Proxy 讀寫分離 read/write splitting</title>
      <link>https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/</link>
      <pubDate>Mon, 29 Mar 2021 08:50:19 +0800</pubDate>
      <guid>https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>如果單純把 DB 分成 Master 及 Slave 並不能真正解決平衡負載問題，因為每台 server 是獨立的 ip，使用者在存取資料庫的時候並不知道要使用哪一台 server 😅；這篇文章會教你如何設定一台 Proxy server，準確地將 SQL 指令正確分流</description>
      <media:content url="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-og_hu_bbca886f617e582f.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>在『<a href="https://deanlin.net/blog/202103/mysql-master-slave-replication/">MySQL Master Slave Replication 主從式架構設定教學</a>』這篇文章的結尾有提到如果單純把 DB 分成 Master 及 Slave 並不能真正解決平衡負載問題，因為每台 server 是獨立的 ip，使用者在存取資料庫的時候並不知道要使用哪一台 server 😅 ，除非你的 Slave server 只是用來做資料分析 🤔；這篇文章會教你如何設定一台 Proxy server，準確地將 SQL 指令分流到正確的 server。</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-01.webp" width="1400" height="761" alt="此為設計架構圖" fetchpriority="high" decoding="async">
  <figcaption>此為設計架構圖</figcaption>
</figure>

<pre tabindex="0"><code>一、前置作業
二、MySQL Proxy 的選擇
三、Atlas 功能介紹
四、在 Ubunut 安裝 Atlas
五、連到 Atlas 管理 server
六、實際測試透過 Atlas Proxy 是否達成讀寫分離
</code></pre><h2 id="一前置作業">一、前置作業<a class="h-anchor" href="#%e4%b8%80%e5%89%8d%e7%bd%ae%e4%bd%9c%e6%a5%ad" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>OS version、MySQL 是筆者安裝時的環境，ip 是給大家參考對應的</p>
</blockquote>
<h3 id="-準備一台要當-mysql-proxy-的-ubuntu-server">準備一台要當 MySQL Proxy 的 ubuntu server<a class="h-anchor" href="#-%e6%ba%96%e5%82%99%e4%b8%80%e5%8f%b0%e8%a6%81%e7%95%b6-mysql-proxy-%e7%9a%84-ubuntu-server" aria-label="這個段落的連結"></a></h3>
<ul>
<li>OS version：ubuntu 16.04</li>
<li>MySQL：5.7</li>
<li>ip ：172.16.9.39</li>
</ul>
<h3 id="-準備一台-master-server-負責寫入">準備一台 Master server 負責寫入<a class="h-anchor" href="#-%e6%ba%96%e5%82%99%e4%b8%80%e5%8f%b0-master-server-%e8%b2%a0%e8%b2%ac%e5%af%ab%e5%85%a5" aria-label="這個段落的連結"></a></h3>
<ul>
<li>OS version：ubuntu 16.04</li>
<li>MySQL：5.7</li>
<li>ip ：172.16.9.32</li>
</ul>
<h3 id="-準備一台以上的-slave-server-負責讀取">準備一台以上的 Slave server 負責讀取<a class="h-anchor" href="#-%e6%ba%96%e5%82%99%e4%b8%80%e5%8f%b0%e4%bb%a5%e4%b8%8a%e7%9a%84-slave-server-%e8%b2%a0%e8%b2%ac%e8%ae%80%e5%8f%96" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>準備兩台以上測試 load balance 才會準確，這塊有興趣的讀者可以自己嘗試</p>
</blockquote>
<ul>
<li>OS version：ubuntu 16.04</li>
<li>MySQL：5.7</li>
<li>ip ：172.16.9.32</li>
</ul>
<h3 id="-在-master-server-裡面增加給-proxy-server-使用的使用者並創建測試用-table">在 Master Server 裡面增加給 Proxy server 使用的使用者，並創建測試用 table<a class="h-anchor" href="#-%e5%9c%a8-master-server-%e8%a3%a1%e9%9d%a2%e5%a2%9e%e5%8a%a0%e7%b5%a6-proxy-server-%e4%bd%bf%e7%94%a8%e7%9a%84%e4%bd%bf%e7%94%a8%e8%80%85%e4%b8%a6%e5%89%b5%e5%bb%ba%e6%b8%ac%e8%a9%a6%e7%94%a8-table" aria-label="這個段落的連結"></a></h3>
<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:#8b949e;font-style:italic">/*建立proxy server使用者*/</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>mysql<span style="color:#ff7b72;font-weight:bold">&gt;</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">USER</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;proxy_account&#39;</span><span style="color:#ff7b72;font-weight:bold">@</span><span style="color:#a5d6ff">&#39;%&#39;</span><span style="color:#6e7681"> </span>IDENTIFIED<span style="color:#6e7681"> </span><span style="color:#ff7b72">BY</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;proxy_password&#39;</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">/*給使用者權限*/</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>mysql<span style="color:#ff7b72;font-weight:bold">&gt;</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">GRANT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">ALL</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">ON</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">*</span>.<span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TO</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;proxy_account&#39;</span><span style="color:#ff7b72;font-weight:bold">@</span><span style="color:#a5d6ff">&#39;%&#39;</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">/*使權限生效*/</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>mysql<span style="color:#ff7b72;font-weight:bold">&gt;</span><span style="color:#6e7681"> </span>FLUSH<span style="color:#6e7681"> </span><span style="color:#ff7b72">PRIVILEGES</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">/*創建測試用資料庫*/</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">DATABASE</span><span style="color:#6e7681"> </span>proxy_test;<span style="color:#6e7681">
</span></span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/3c818b9e9a1294cd7e33f108985884e0" rel="noopener">create_proxy_user.sql</a>（GitHub Gist）</p>
<h2 id="二mysql-proxy-的選擇">二、MySQL Proxy 的選擇<a class="h-anchor" href="#%e4%ba%8cmysql-proxy-%e7%9a%84%e9%81%b8%e6%93%87" aria-label="這個段落的連結"></a></h2>
<ul>
<li><strong>MySQL Proxy</strong>：原本想使用官方工具來處理讀寫分離，但一上<strong>官網發現連他自己都不推薦使用</strong>😱，所以就不在多費心力研究。</li>
</ul>
<p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-02.webp" width="1296" height="398" alt="二、MySQL Proxy 的選擇（圖 2）" loading="lazy" decoding="async"></p>
<ul>
<li><strong>MySQL Router</strong>：根據官方的指引認真的研究這個工具後發現雖然設定上簡單，但並不是很符合需求，他的<strong>讀寫分離是同一台伺服器分成兩個 port 來處理</strong>，雖然現在主流框架都支援這樣的操作，但我認為這樣的方式並不優雅</li>
<li><strong>kingshard</strong>：是基於 Go 語言開發的 MySQL Proxy，在 github 上面擁有高人氣；但實際使用心得真的是滿滿的陰影…<strong>官方安裝指引非常的爛</strong>，鬼才知道要自己切換 branch 才能使用啊？設定檔根本要用錯誤嘗試法，沒說清楚必填選填，上面這些問題也就罷了，安裝成功後居然告訴我只有 windows 的電腦可以測試 <a href="https://github.com/flike/kingshard/issues/591#issuecomment-776367532">mac 電腦不行</a>😡，作者是在開玩笑嗎？總之我非常的不推薦。</li>
<li><strong>Atlas</strong>：是基於 MySQL-Proxy 0.8.2 版本開發的工具，由 360 公司推出，在 github 上面找到最新的版本是 2015 年推出的，至於為什麼不再更新的理由，360 公司表示因為這個工具已經夠棒了🥴，沒什麼繼續優化的必要性了；事實上這個工具也是筆者最後選用的，儘管安裝過程滿滿的坑😓</li>
</ul>
<h2 id="三atlas-功能介紹">三、Atlas 功能介紹<a class="h-anchor" href="#%e4%b8%89atlas-%e5%8a%9f%e8%83%bd%e4%bb%8b%e7%b4%b9" aria-label="這個段落的連結"></a></h2>
<ol>
<li>讀寫分離</li>
<li>slave server 的 load balance</li>
<li>IP filter</li>
<li>DBA 可以輕鬆的上下線 DB（有指令支持）</li>
<li>自動移除當機的 DB（但如果當機的是 master server，就只剩讀取功能）</li>
</ol>
<h2 id="四在-ubunut-安裝-atlas">四、在 Ubunut 安裝 Atlas<a class="h-anchor" href="#%e5%9b%9b%e5%9c%a8-ubunut-%e5%ae%89%e8%a3%9d-atlas" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>筆者在安裝過程中感受到滿滿的惡意</p>
</blockquote>
<p><strong>STEP 1</strong>：下載 &amp; 安裝 Atlas</p>
<p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-03.webp" width="1259" height="340" alt="四、在 Ubunut 安裝 Atlas（圖 3）" loading="lazy" decoding="async"></p>
<p>你可以在官方 <a href="https://github.com/Qihoo360/Atlas/releases">github release </a>中找到 deb 的安裝包下載他</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-bash" data-lang="bash"><span style="display:flex;"><span>wget https://github.com/Qihoo360/Atlas/releases/download/2.2/Atlas-2.2-debian6.0-x86_64.deb
</span></span></code></pre></div><p>執行安裝</p>
<pre tabindex="0"><code>dpkg -i Atlas-2.2-debian6.0-x86_64.deb
</code></pre><p><strong>STEP 2</strong>：調整 Atlas 的設定檔</p>
<p>Atlas 的安裝路徑是/usr/local/mysql-proxy ，執行時的參數會參考 conf 目錄下的 test.cnf 檔案</p>
<pre tabindex="0"><code>vi /usr/local/mysql-proxy/conf/test.cnf
</code></pre><p>下面幾個需要特別調整，其他用默認的就可以了</p>
<ul>
<li><strong>proxy-backend-addresses</strong>：填寫 Master server 的 ip</li>
</ul>
<p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-04.webp" width="1196" height="88" alt="四、在 Ubunut 安裝 Atlas（圖 4）" loading="lazy" decoding="async"></p>
<ul>
<li><strong>proxy-read-only-backend-addresses</strong>：填寫 Slave server 的 ip</li>
</ul>
<p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-05.webp" width="1400" height="122" alt="四、在 Ubunut 安裝 Atlas（圖 5）" loading="lazy" decoding="async"></p>
<ul>
<li><strong>pwds</strong>：請使用於前置作業 Master server 新增專門給 Proxy Server 使用的帳號密碼，密碼要用下面的指令作加密</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-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 前往加密的目錄</span>
</span></span><span style="display:flex;"><span>cd /usr/local/mysql-proxy/bin
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 對密碼加密</span>
</span></span><span style="display:flex;"><span>./encrypt proxy_password
</span></span></code></pre></div><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-06.webp" width="1332" height="126" alt="四、在 Ubunut 安裝 Atlas（圖 6）" loading="lazy" decoding="async"></p>
<p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-07.webp" width="1400" height="136" alt="四、在 Ubunut 安裝 Atlas（圖 7）" loading="lazy" decoding="async"></p>
<ul>
<li><strong>sql-log</strong>：預設是 OFF 不會紀錄，但為了方便觀察讀寫是否分離，我們將它設定為 REALTIME</li>
</ul>
<p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-08.webp" width="1400" height="138" alt="四、在 Ubunut 安裝 Atlas（圖 8）" loading="lazy" decoding="async"></p>
<p><strong>STEP 3</strong>：啟動 Atlas</p>
<p>輸入下面指令即可啟動 Atlas</p>
<pre tabindex="0"><code>/usr/local/mysql-proxy/bin/mysql-proxyd test start
</code></pre><p>不過應該有 99% 的人會在這個步驟失敗</p>
<figure class="figure"><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-09.webp" width="1400" height="90" alt="這是另外一種啟動方式，反正只是給大家看會有什麼錯誤而已" loading="lazy" decoding="async">
  <figcaption>這是另外一種啟動方式，反正只是給大家看會有什麼錯誤而已</figcaption>
</figure>

<p>你可以在 log 裡面看錯誤訊息</p>
<pre tabindex="0"><code>vi /usr/local/mysql-proxy/log/test.log
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-10.webp" width="1400" height="192" alt="四、在 Ubunut 安裝 Atlas（圖 10）" loading="lazy" decoding="async"></p>
<p><strong>解決方式：</strong></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-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 安裝套件相關套件</span>
</span></span><span style="display:flex;"><span>apt-get install libmysqlclient-dev
</span></span><span style="display:flex;"><span>apt-get install mysql-client
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 下載缺失的 libmysqlclient.so.16 套件，我是在這個網站找到的</span>
</span></span><span style="display:flex;"><span>wget http://files.directadmin.com/services/debian_5.0_64/libmysqlclient.so.16
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># 將 libmysqlclient.so.16 搬移到 lib 目錄下</span>
</span></span><span style="display:flex;"><span>mv libmysqlclient.so.16 /usr/lib/x86_64-linux-gnu/
</span></span></code></pre></div><p>再次啟動 Atlas</p>
<pre tabindex="0"><code>/usr/local/mysql-proxy/bin/mysql-proxyd test start
</code></pre><p>看到 <strong>OK: MySQL-Proxy of test is started</strong> 的提示就成功嚕！</p>
<p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-11.webp" width="1400" height="73" alt="四、在 Ubunut 安裝 Atlas（圖 11）" loading="lazy" decoding="async"></p>
<p>檢查 Atlas 服務狀態</p>
<pre tabindex="0"><code>/usr/local/mysql-proxy/bin/mysql-proxyd test status
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-12.webp" width="1400" height="104" alt="四、在 Ubunut 安裝 Atlas（圖 12）" loading="lazy" decoding="async"></p>
<h2 id="五連到-atlas-管理-server">五、連到 Atlas 管理 server<a class="h-anchor" href="#%e4%ba%94%e9%80%a3%e5%88%b0-atlas-%e7%ae%a1%e7%90%86-server" aria-label="這個段落的連結"></a></h2>
<p>Atlas 預設的管理 port 為 2345，ip 填寫 Proxy server ip，如果 admin 的帳號密碼沒換就是跟下面一樣</p>
<pre tabindex="0"><code>mysql -h172.16.9.39 -P2345 -uuser -ppwd
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-13.webp" width="1400" height="553" alt="五、連到 Atlas 管理 server（圖 13）" loading="lazy" decoding="async"></p>
<h3 id="-查看-atlas-的管理命令">查看 Atlas 的管理命令<a class="h-anchor" href="#-%e6%9f%a5%e7%9c%8b-atlas-%e7%9a%84%e7%ae%a1%e7%90%86%e5%91%bd%e4%bb%a4" aria-label="這個段落的連結"></a></h3>
<pre tabindex="0"><code>mysql&gt; select * from help;
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-14.webp" width="1400" height="529" alt="查看 Atlas 的管理命令（圖 14）" loading="lazy" decoding="async"></p>
<h3 id="-查看-mysql-讀寫分離設定是否生效">查看 MySQL 讀寫分離設定是否生效<a class="h-anchor" href="#-%e6%9f%a5%e7%9c%8b-mysql-%e8%ae%80%e5%af%ab%e5%88%86%e9%9b%a2%e8%a8%ad%e5%ae%9a%e6%98%af%e5%90%a6%e7%94%9f%e6%95%88" aria-label="這個段落的連結"></a></h3>
<p>rw 為讀寫權限，ro 代表只有讀的權限</p>
<pre tabindex="0"><code>mysql&gt; select * from backends;
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-15.webp" width="942" height="320" alt="查看 MySQL 讀寫分離設定是否生效（圖 15）" loading="lazy" decoding="async"></p>
<p>到這步其實就可以完結撒花惹 😊😊😊😊😊😊</p>
<h2 id="六實際測試透過-atlas-proxy-是否達成讀寫分離">六、實際測試透過 Atlas Proxy 是否達成讀寫分離<a class="h-anchor" href="#%e5%85%ad%e5%af%a6%e9%9a%9b%e6%b8%ac%e8%a9%a6%e9%80%8f%e9%81%8e-atlas-proxy-%e6%98%af%e5%90%a6%e9%81%94%e6%88%90%e8%ae%80%e5%af%ab%e5%88%86%e9%9b%a2" aria-label="這個段落的連結"></a></h2>
<p>Atlas 預設的proxy port 為 1234，ip 填寫 Proxy server ip，登入的帳號密碼填寫你在 Master 申請給 Atlas Proxy 使用的那組</p>
<pre tabindex="0"><code>mysql -h172.16.9.39 -P1234 -uproxy_account -pproxy_password
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-16.webp" width="1400" height="536" alt="六、實際測試透過 Atlas Proxy 是否達成讀寫分離（圖 16）" loading="lazy" decoding="async"></p>
<h3 id="-測試寫入功能">測試寫入功能<a class="h-anchor" href="#-%e6%b8%ac%e8%a9%a6%e5%af%ab%e5%85%a5%e5%8a%9f%e8%83%bd" aria-label="這個段落的連結"></a></h3>
<pre tabindex="0"><code># 先選擇之前創建的測試資料庫
mysql&gt; use proxy_test;
# 創建表並塞入一寫資料
mysql&gt; CREATE TABLE department (id INT,name varchar(50));
mysql&gt; INSERT INTO department VALUES (1, &#39;Sales&#39;);
mysql&gt; INSERT INTO department VALUES (2, &#39;Operator&#39;);
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-17.webp" width="1062" height="308" alt="測試寫入功能（圖 17）" loading="lazy" decoding="async"></p>
<h3 id="-測試讀取功能">測試讀取功能<a class="h-anchor" href="#-%e6%b8%ac%e8%a9%a6%e8%ae%80%e5%8f%96%e5%8a%9f%e8%83%bd" aria-label="這個段落的連結"></a></h3>
<pre tabindex="0"><code># 先選擇之前創建的測試資料庫
mysql&gt; use proxy_test;
# 讀取剛剛創建的 table
mysql&gt; SELECT * FROM department;
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-18.webp" width="536" height="308" alt="測試讀取功能（圖 18）" loading="lazy" decoding="async"></p>
<h3 id="-觀察-sql-運行的-log">觀察 sql 運行的 log<a class="h-anchor" href="#-%e8%a7%80%e5%af%9f-sql-%e9%81%8b%e8%a1%8c%e7%9a%84-log" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>記得把 Atla 設定檔的 ssql-log 參數調整為 REALTIME 才會有喔！</p>
</blockquote>
<p>前往 log file</p>
<pre tabindex="0"><code>tail /usr/local/mysql-proxy/log/sql_test.log
</code></pre><p>從下圖我們可以觀察到，負責變更資料庫的是 Master server(172.16.3.32)，讀取的則是 Slave Server(172.16.3.33)，恭喜自己大功告成</p>
<p><img src="https://deanlin.net/blog/202103/mysql-read-write-splitting-atlas/images/mysql-read-write-splitting-atlas-19.webp" width="1400" height="530" alt="觀察 sql 運行的 log（圖 19）" loading="lazy" decoding="async"></p>
<hr>
<p>希望這篇文章有幫助到使用 Ubuntu 架設 MySQL 並且想做到讀寫分離的開發人員，網路上對這塊的資訊相對來說比較零散，有些甚至都是錯誤，整理起來這些文件也真的很費力，如果對你有幫助的話可以幫我按個拍手👏給我一點繼續分享技術文章的動力。</p>
<pre tabindex="0"><code>如果你想了解資料庫主從式架構的設定歡迎參考我的文章：
👍 MySQL Master Slave Replication 主從式架構設定教學
👍 在 MySQL 5.7 解決主從複製的延遲問題
</code></pre><hr>
<p><strong>參考資訊</strong>：<a href="https://web.archive.org/web/20170127083654/http://files.directadmin.com:80/services/debian_5.0_64/libmysqlclient.so.16" title="原網頁已失效，連到網際網路檔案館的存檔">下載libmysqlclient.so.16檔案</a>、<a href="https://www.jianshu.com/p/67d4b34b2af4">讀寫分離測試</a>、<a href="https://zhuanlan.zhihu.com/p/94279867">Mysql访问中间件 — Atlas初探</a>、<a href="https://www.jianshu.com/p/b68e429d09c7">Atlas</a>、<a href="https://github.com/Qihoo360/Atlas/blob/master/README_ZH.md">Atlas Github</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>MySQL Master Slave Replication 主從式架構設定教學</title>
      <link>https://deanlin.net/blog/202103/mysql-master-slave-replication/</link>
      <pubDate>Thu, 25 Mar 2021 16:45:58 +0800</pubDate>
      <guid>https://deanlin.net/blog/202103/mysql-master-slave-replication/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>Master Slave Replication 是一個 Web 工程師需要具備的基礎概念，我們可以透過這個技術有效的降低 Database server 負荷，同時給使用者提供更穩定順暢的體驗；本文除了讓你了解這個技術的原理外還會以 MySQL…</description>
      <media:content url="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-og_hu_19e34a7748bd18bb.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-01.webp" width="1400" height="783" alt="MySQL Master Slave Replication 主從式架構設定教學（圖 1）" fetchpriority="high" decoding="async"></p>
<p>Master Slave Replication 是一個 Web 工程師需要具備的基礎概念，我們可以透過這個技術有效的降低 Database server 負荷，同時給使用者提供更穩定順暢的體驗；本文除了讓你了解這個技術的原理外還會以 MySQL 這門資料庫語言一步步透過實作讓你理解如何實現它。</p>
<pre tabindex="0"><code>一、30 秒認識 Master Slave Replication 是什麼
  ∙ 為什麼需要 Master Slave Replication 的架構？
  ∙ Master Slave Replication 為什麼能夠解決這些問題？
  ∙ Master Slave Replication 的架構
二、實作 MySQL Master Slave replication
  ∙ 前置作業
  ∙ 建立MySQL Replication
三、驗證 Master、Slave DB 是否同步
</code></pre><h2 id="一30-秒認識-master-slave-replication-是什麼">一、30 秒認識 Master Slave Replication 是什麼<a class="h-anchor" href="#%e4%b8%8030-%e7%a7%92%e8%aa%8d%e8%ad%98-master-slave-replication-%e6%98%af%e4%bb%80%e9%ba%bc" aria-label="這個段落的連結"></a></h2>
<h3 id="為什麼需要-master-slave-replication-的架構">為什麼需要 Master Slave Replication 的架構？<a class="h-anchor" href="#%e7%82%ba%e4%bb%80%e9%ba%bc%e9%9c%80%e8%a6%81-master-slave-replication-%e7%9a%84%e6%9e%b6%e6%a7%8b" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>網站需要面對大流量</strong>：新聞媒體類的網站無論哪個時段都會被大量的檢索，如果資料庫只用一個 server 來處理會無法負荷瞬間的高流量。</li>
<li><strong>資料庫內容需要被分析</strong>：公司如果有資料分析部門，他們也會隨時監控資料庫的變化來產生報表，但如果跟一般使用者一樣 query 同一台 server 會使這台 server 增加不必要的負荷導致使用者體驗下降。</li>
</ul>
<h3 id="master-slave-replication-為什麼能夠解決這些問題">Master Slave Replication 為什麼能夠解決這些問題？<a class="h-anchor" href="#master-slave-replication-%e7%82%ba%e4%bb%80%e9%ba%bc%e8%83%bd%e5%a4%a0%e8%a7%a3%e6%b1%ba%e9%80%99%e4%ba%9b%e5%95%8f%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<ul>
<li><strong>主從式架構</strong>：將資料庫的 server 區分成 Master(1台) 與 Slave(N台) 兩種，Master 負責資料寫入，Slave 提供資料檢索；這樣就把壓力分散到幾台 Server 上面。</li>
<li><strong>資料庫同步</strong>：Slave 會同步 Master 的資料，這樣除了分散 server 壓力外，資料也獲得了備份。</li>
</ul>
<h3 id="master-slave-replication-的架構">Master Slave Replication 的架構<a class="h-anchor" href="#master-slave-replication-%e7%9a%84%e6%9e%b6%e6%a7%8b" aria-label="這個段落的連結"></a></h3>
<ol>
<li>在 master 的 server 執行的 SQL command 會被記錄在 Binary Log 裡面</li>
<li>master 將 Binary Log 傳送到 slave 的 Relay Log</li>
<li>slave 依據 Relay Log 做資料的變更</li>
</ol>
<figure class="figure"><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-02.webp" width="1194" height="738" alt="Relay Log 與 Binary Log檔案格式、內容都一樣，不同在於 slave 在執行完 Relay Log 的 SQL 之後，會自己刪除當前的 Relay Log" loading="lazy" decoding="async">
  <figcaption>Relay Log 與 Binary Log檔案格式、內容都一樣，不同在於 slave 在執行完 Relay Log 的 SQL 之後，會自己刪除當前的 Relay Log</figcaption>
</figure>

<h2 id="二實作-mysql-master-slave-replication">二、實作 MySQL Master Slave replication<a class="h-anchor" href="#%e4%ba%8c%e5%af%a6%e4%bd%9c-mysql-master-slave-replication" aria-label="這個段落的連結"></a></h2>
<h2 id="前置作業">前置作業<a class="h-anchor" href="#%e5%89%8d%e7%bd%ae%e4%bd%9c%e6%a5%ad" aria-label="這個段落的連結"></a></h2>
<h3 id="-準備兩台-ubuntu-server-自己實驗建議使用-vmware筆者環境配置如下">準備兩台 ubuntu server (自己實驗建議使用 VMWARE)，筆者環境配置如下<a class="h-anchor" href="#-%e6%ba%96%e5%82%99%e5%85%a9%e5%8f%b0-ubuntu-server-%e8%87%aa%e5%b7%b1%e5%af%a6%e9%a9%97%e5%bb%ba%e8%ad%b0%e4%bd%bf%e7%94%a8-vmware%e7%ad%86%e8%80%85%e7%92%b0%e5%a2%83%e9%85%8d%e7%bd%ae%e5%a6%82%e4%b8%8b" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>硬體配置一定會影響到效能，筆者只是建置一個提供實驗的環境而已</p>
</blockquote>
<ul>
<li>OS version：ubuntu 16.04</li>
<li>RAM：1G</li>
<li>ROM：8G</li>
</ul>
<h3 id="-給兩台-sever-安裝-mysql">給兩台 sever 安裝 MySQL<a class="h-anchor" href="#-%e7%b5%a6%e5%85%a9%e5%8f%b0-sever-%e5%ae%89%e8%a3%9d-mysql" aria-label="這個段落的連結"></a></h3>
<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-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update
</span></span><span style="display:flex;"><span>sudo apt install mysql-server
</span></span></code></pre></div><p>安裝完成後你可以先登入 MySQL 看看安裝版本資訊</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo mysql -u root -p
</span></span></code></pre></div><p><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-03.webp" width="1400" height="491" alt="給兩台 sever 安裝 MySQL（圖 3）" loading="lazy" decoding="async"></p>
<h2 id="建立mysql-replication">建立MySQL Replication<a class="h-anchor" href="#%e5%bb%ba%e7%ab%8bmysql-replication" aria-label="這個段落的連結"></a></h2>
<h3 id="-master-server-設定">Master server 設定<a class="h-anchor" href="#-master-server-%e8%a8%ad%e5%ae%9a" aria-label="這個段落的連結"></a></h3>
<p><strong>STEP 1</strong>：設定 Master server 的 my.cnf</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo vi /etc/mysql/my.cnf
</span></span></code></pre></div><pre tabindex="0"><code>[mysqld]
# 在這裡設定已蓋過預設的127.0.0.1(只能連本機)
bind-address = 0.0.0.0

# id為唯一值，每個server不可重覆
server-id = 1

# 指定產生bin log檔案名稱，使MySQL記錄所有DB交易記錄
log-bin=mysql-bin
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/a555262a58eb9245c7ffcbcf891791ca" rel="noopener">my.cnf</a>（GitHub Gist）</p>
<p>儲存後，重啟 MySQL 才會生效</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo /etc/init.d/mysql restart
</span></span></code></pre></div><p><strong>STEP 2</strong>：在 Master server 建立給 Slave 複製資料的帳號</p>
<p>登入 MySQL</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo mysql -u root -p
</span></span></code></pre></div><p>建立有 REPLICATION、SLAVE 權限的 user 帳號</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:#8b949e;font-style:italic">/*建立使用者*/</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>mysql<span style="color:#ff7b72;font-weight:bold">&gt;</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">USER</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;account&#39;</span><span style="color:#ff7b72;font-weight:bold">@</span><span style="color:#a5d6ff">&#39;ip&#39;</span><span style="color:#6e7681"> </span>IDENTIFIED<span style="color:#6e7681"> </span><span style="color:#ff7b72">BY</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;password&#39;</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">/*給使用者權限*/</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>mysql<span style="color:#ff7b72;font-weight:bold">&gt;</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">GRANT</span><span style="color:#6e7681"> </span>REPLICATION<span style="color:#6e7681"> </span>SLAVE<span style="color:#6e7681"> </span><span style="color:#ff7b72">ON</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">*</span>.<span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TO</span><span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;account&#39;</span><span style="color:#ff7b72;font-weight:bold">@</span><span style="color:#a5d6ff">&#39;ip&#39;</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">/*使權限生效*/</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>mysql<span style="color:#ff7b72;font-weight:bold">&gt;</span><span style="color:#6e7681"> </span>FLUSH<span style="color:#6e7681"> </span><span style="color:#ff7b72">PRIVILEGES</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/68f42b828e24df0209600dee7a7030c7" rel="noopener">create_slave_user.sql</a>（GitHub Gist）</p>
<p><strong>STEP 3</strong>：備份 Master server 的 DB</p>
<blockquote>
<p>執行此步驟時如果系統已經上線，一定要發出公告說明系統維護時間</p>
</blockquote>
<p>在完成 DB Replication 前，為了保證 Master 與 Slave 的資料相同，你需要先把 DB Lock 成 read only：</p>
<pre tabindex="0"><code>mysql&gt; FLUSH TABLES WITH READ LOCK;
</code></pre><p>備份指令：</p>
<pre tabindex="0"><code>mysqldump -uroot -p  --all-databases &gt; master.sql
</code></pre><p>解除 read only：</p>
<pre tabindex="0"><code>mysql&gt; UNLOCK TABLES;
</code></pre><p><strong>STEP 4</strong>：檢查 Master 是否已經啟動</p>
<pre tabindex="0"><code>mysql&gt; SHOW MASTER STATUS \G
</code></pre><p><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-04.webp" width="1142" height="330" alt="Master server 設定（圖 4）" loading="lazy" decoding="async"></p>
<h3 id="-slave-server-設定">Slave server 設定<a class="h-anchor" href="#-slave-server-%e8%a8%ad%e5%ae%9a" aria-label="這個段落的連結"></a></h3>
<p><strong>STEP 1</strong>：將剛剛備份的 <code>master.sql</code> 還原到 Slave server 的 DB</p>
<p>先複製 master.sql 到 Slave server 的目錄下</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo scp master.sql &lt;username&gt;@&lt;IP or hostname&gt;:&lt;Destination&gt;
</span></span></code></pre></div><p>下指令還原 DB</p>
<pre tabindex="0"><code>mysql -u root -p — default-character-set=utf8 &lt; master.sql
</code></pre><h3 id="step-2設定-slave-server-的-mycnf">STEP 2：設定 Slave server 的 my.cnf<a class="h-anchor" href="#step-2%e8%a8%ad%e5%ae%9a-slave-server-%e7%9a%84-mycnf" aria-label="這個段落的連結"></a></h3>
<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-bash" data-lang="bash"><span style="display:flex;"><span>sudo vi /etc/mysql/my.cnf
</span></span></code></pre></div><pre tabindex="0"><code>[mysqld]
#注意：不可與剛剛設定的 Master 重複
server-id=2 

#Slave server 的 DB 開啟唯獨避免被寫入
read-only=on
</code></pre><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/54144c2099368bfc91f2681a47f9bf94" rel="noopener">slave_my.cnf</a>（GitHub Gist）</p>
<p>儲存後，重啟 MySQL 才會生效</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo /etc/init.d/mysql restart
</span></span></code></pre></div><p><strong>STEP 3</strong>：設定 SlaveDB，指定 Master DB 登入資訊，並啟動 Replication</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:#8b949e;font-style:italic">/* 這裡的帳號密碼就上面 Master 設定的那組 */</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>mysql<span style="color:#ff7b72;font-weight:bold">&gt;</span><span style="color:#6e7681"> </span>CHANGE<span style="color:#6e7681"> </span>MASTER<span style="color:#6e7681"> </span><span style="color:#ff7b72">TO</span><span style="color:#6e7681"> </span>MASTER_HOST<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;master_ip&#39;</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span>MASTER_USER<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;account&#39;</span>,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">              </span>MASTER_PASSWORD<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;password&#39;</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/e09739d509230e0b270f7b374c8ba553" rel="noopener">setting_slave_info.sql</a>（GitHub Gist）</p>
<p>啟動 Slave Replication：</p>
<pre tabindex="0"><code>mysql&gt; START SLAVE;
</code></pre><p><strong>STEP 4</strong>：檢查 replication 狀態</p>
<pre tabindex="0"><code>SHOW SLAVE STATUS \G
</code></pre><p>如果下圖紅框皆顯示 Yes 就代表你成功嚕！</p>
<p><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-05.webp" width="582" height="268" alt="STEP 2：設定 Slave server 的 my.cnf（圖 5）" loading="lazy" decoding="async"></p>
<p>如果你看到下面這個畫面，可能是因為你的 Master MySQL 的設定有問題，最有可能是『使用者帳號、密碼錯誤』、『bind-address 沒有調整』</p>
<p><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-06.webp" width="582" height="268" alt="STEP 2：設定 Slave server 的 my.cnf（圖 6）" loading="lazy" decoding="async"></p>
<h2 id="三驗證-masterslave-db-是否同步">三、驗證 Master、Slave DB 是否同步<a class="h-anchor" href="#%e4%b8%89%e9%a9%97%e8%ad%89-masterslave-db-%e6%98%af%e5%90%a6%e5%90%8c%e6%ad%a5" aria-label="這個段落的連結"></a></h2>
<p>先在 Master 確認是不是真的有跟 Slave 串接成功</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">show</span><span style="color:#6e7681"> </span>slave<span style="color:#6e7681"> </span>hosts;<span style="color:#6e7681">
</span></span></span></code></pre></div><p><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-07.webp" width="1400" height="274" alt="三、驗證 Master、Slave DB 是否同步（圖 7）" loading="lazy" decoding="async"></p>
<p>確認連接成功後我們在 Master server 上面新增一個 DB</p>
<p><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-08.webp" width="342" height="479" alt="三、驗證 Master、Slave DB 是否同步（圖 8）" loading="lazy" decoding="async"></p>
<p>回到 Slave server 就會看到 DB 被同步過來嚕～</p>
<p><img src="https://deanlin.net/blog/202103/mysql-master-slave-replication/images/mysql-master-slave-replication-09.webp" width="342" height="217" alt="三、驗證 Master、Slave DB 是否同步（圖 9）" loading="lazy" decoding="async"></p>
<hr>
<p>這篇文章是針對 MySQL Master Slave Replication 做說明，但你依照上面的做法完成後還需要思考幾個問題：</p>
<ul>
<li>我們知道 Master server 是用來更新資料，Slave server 是用來檢索資料；但他們都是獨立的 ip，總不能每個 api 執行時都連不一樣的 ip 吧？</li>
<li>Slave 的資料是從 Master 上複製過來的，會複製就代表會有延遲，要如何保持資料的一致性呢？</li>
</ul>
<pre tabindex="0"><code>如果你想深入了解上面的問題如何解決可以參考我的文章：
👍 在 MySQL 5.7 解決主從複製的延遲問題
👍 Ubuntu 用 Atlas 實現 MySQL Proxy 讀寫分離 read/write splitting
</code></pre><hr>
<p>參考資源：<a href="https://dotblogs.com.tw/eric_obay_talk/2018/10/24/160017">MySQL Master Slave replication</a>、<a href="https://ithelp.ithome.com.tw/articles/10236724">打造MySQL Replication</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>線上 SQL 語法測試網站(SQL fiddle、MySQL Online)</title>
      <link>https://deanlin.net/blog/202102/online-sql-playgrounds/</link>
      <pubDate>Fri, 26 Feb 2021 13:16:12 +0800</pubDate>
      <guid>https://deanlin.net/blog/202102/online-sql-playgrounds/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>當朋友問你 SQL 語法問題時如果沒有現成的資料庫真的很難幫上忙，並且也不確定自己的回答是不是真的能解決問題；今天就來介紹兩個我常用的 SQL 語法測試網站，並透過解說朋友遇到的題目讓大家快速了解網站的使用方式。</description>
      <media:content url="https://deanlin.net/blog/202102/online-sql-playgrounds/images/online-sql-playgrounds-og_hu_1ab28ebc893ef471.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<p>當朋友問你 SQL 語法問題時如果沒有現成的資料庫真的很難幫上忙，並且也不確定自己的回答是不是真的能解決問題；今天就來介紹兩個我常用的 SQL 語法測試網站，並透過解說朋友遇到的題目讓大家快速了解網站的使用方式。</p>
<pre tabindex="0"><code>一、SQL Fiddle 簡介
二、MySQL Online 簡介
三、題目：找出每個 department 中 score 最高的兩個人並以 score 進行排序
  1. 用 SQL Fiddle 解題
  2. 用 MySQL Online 解題
</code></pre><p><img src="https://deanlin.net/blog/202102/online-sql-playgrounds/images/online-sql-playgrounds-01.webp" width="1400" height="783" alt="線上 SQL 語法測試網站(SQL fiddle、MySQL Online)（圖 1）" fetchpriority="high" decoding="async"></p>
<h2 id="一sql-fiddle-簡介">一、SQL Fiddle 簡介<a class="h-anchor" href="#%e4%b8%80sql-fiddle-%e7%b0%a1%e4%bb%8b" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p><strong>網址：<a href="http://sqlfiddle.com/">http://sqlfiddle.com/</a></strong></p>
</blockquote>
<h3 id="支援資料庫版本">支援資料庫版本：<a class="h-anchor" href="#%e6%94%af%e6%8f%b4%e8%b3%87%e6%96%99%e5%ba%ab%e7%89%88%e6%9c%ac" aria-label="這個段落的連結"></a></h3>
<a class="linkcard" href="https://sheetsu.com/tables/507fea70b1" target="_blank" rel="noopener">
  <span class="lc-body">
    <span class="lc-title">Table</span>
    <span class="lc-site"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M7 17 17 7M8.5 7H17v8.5"/></svg>sheetsu.com</span>
  </span>
</a>

<h3 id="介面說明">介面說明：<a class="h-anchor" href="#%e4%bb%8b%e9%9d%a2%e8%aa%aa%e6%98%8e" aria-label="這個段落的連結"></a></h3>
<ol>
<li><strong>選擇資料庫及版本</strong></li>
<li><strong>DDL 區域</strong>：CREATE TABLE, INSERT 都在這個地方輸入，按下『Build Schema』即可生成 Schema；<code><strong>要先生成Schema才能執行SQL</strong></code></li>
<li><strong>DML 區域</strong>：資料庫操作語言區域</li>
<li><strong>Result</strong>：顯示執行結果</li>
</ol>
<p><img src="https://deanlin.net/blog/202102/online-sql-playgrounds/images/online-sql-playgrounds-02.webp" width="1399" height="762" alt="介面說明：（圖 2）" loading="lazy" decoding="async"></p>
<h2 id="二mysql-online-簡介">二、MySQL Online 簡介<a class="h-anchor" href="#%e4%ba%8cmysql-online-%e7%b0%a1%e4%bb%8b" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p><strong>網址：<a href="https://paiza.io/en/projects/new?language=mysql">https://paiza.io/en/projects/new?language=mysql</a></strong></p>
</blockquote>
<h3 id="介面說明-1">介面說明：<a class="h-anchor" href="#%e4%bb%8b%e9%9d%a2%e8%aa%aa%e6%98%8e-1" aria-label="這個段落的連結"></a></h3>
<ol>
<li><strong>選擇測試語言</strong>：除了 MySQL 外，他還能測試 C、Java、R、php…</li>
<li><strong>程式碼區域</strong>：他的DDL與DML是混合在一起的，所以要測試就全部貼上去</li>
<li><strong>Result</strong>：顯示執行結果</li>
</ol>
<p><img src="https://deanlin.net/blog/202102/online-sql-playgrounds/images/online-sql-playgrounds-03.webp" width="1399" height="882" alt="介面說明：（圖 3）" loading="lazy" decoding="async"></p>
<h2 id="三題目找出每個-department-中-score-最高的兩個人並符合輸出結果的排序">三、題目：找出每個 department 中 score 最高的兩個人，並符合輸出結果的排序<a class="h-anchor" href="#%e4%b8%89%e9%a1%8c%e7%9b%ae%e6%89%be%e5%87%ba%e6%af%8f%e5%80%8b-department-%e4%b8%ad-score-%e6%9c%80%e9%ab%98%e7%9a%84%e5%85%a9%e5%80%8b%e4%ba%ba%e4%b8%a6%e7%ac%a6%e5%90%88%e8%bc%b8%e5%87%ba%e7%b5%90%e6%9e%9c%e7%9a%84%e6%8e%92%e5%ba%8f" aria-label="這個段落的連結"></a></h2>
<h3 class="h-nomark" id="-生成題目資料表的-sql-">⚒️ 生成題目資料表的 SQL :<a class="h-anchor" href="#-%e7%94%9f%e6%88%90%e9%a1%8c%e7%9b%ae%e8%b3%87%e6%96%99%e8%a1%a8%e7%9a%84-sql-" aria-label="這個段落的連結"></a></h3>
<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>employee<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>INT,<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">50</span>),<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>score<span style="color:#6e7681"> </span>INT,<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#6e7681">  </span>department_id<span style="color:#6e7681">  </span>INT<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">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">TABLE</span><span style="color:#6e7681"> </span>department<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>INT,<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">50</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">INSERT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">INTO</span><span style="color:#6e7681"> </span>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">1</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;John&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">83</span>,<span style="color:#a5d6ff">1</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">2</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Yannis&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">88</span>,<span style="color:#a5d6ff">2</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">3</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Victor&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">83</span>,<span style="color:#a5d6ff">1</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">4</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Jimmy&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">83</span>,<span style="color:#a5d6ff">3</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">5</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Richard&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">88</span>,<span style="color:#a5d6ff">1</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">6</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Hens&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">82</span>,<span style="color:#a5d6ff">2</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">7</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Peter&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">89</span>,<span style="color:#a5d6ff">2</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">8</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Brian&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">77</span>,<span style="color:#a5d6ff">1</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">9</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Kelvin&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">90</span>,<span style="color:#a5d6ff">3</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>employee<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">10</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Ben&#39;</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">80</span>,<span style="color:#a5d6ff">3</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">INSERT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">INTO</span><span style="color:#6e7681"> </span>department<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">1</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Sales&#39;</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>department<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">2</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Operator&#39;</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>department<span style="color:#6e7681"> </span><span style="color:#ff7b72">VALUES</span><span style="color:#6e7681"> </span>(<span style="color:#a5d6ff">3</span>,<span style="color:#6e7681"> </span><span style="color:#a5d6ff">&#39;Development&#39;</span>);<span style="color:#6e7681">
</span></span></span></code></pre></div><p class="code-source">原始碼：<a href="https://gist.github.com/dean9703111/1a1e64627d699759e01e8f15fdf50518" rel="noopener">employee_score_question.sql</a>（GitHub Gist）</p>
<h3 id="目標輸出結果">目標輸出結果：<a class="h-anchor" href="#%e7%9b%ae%e6%a8%99%e8%bc%b8%e5%87%ba%e7%b5%90%e6%9e%9c" aria-label="這個段落的連結"></a></h3>
<a class="linkcard" href="https://sheetsu.com/tables/339e86faf8" target="_blank" rel="noopener">
  <span class="lc-body">
    <span class="lc-title">Table</span>
    <span class="lc-site"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M7 17 17 7M8.5 7H17v8.5"/></svg>sheetsu.com</span>
  </span>
</a>

<blockquote class="pullquote"><p>這個題目表面上看起來很簡單，實際做起來會遇到一些沒想到的問題，有興趣的小夥伴建議自己先玩玩不要直接看答案，文章後面也會說明這兩種編輯器的最大差異</p>
</blockquote>
<hr>
<h3 class="h-nomark" id="1-用-sql-fiddle-解題">1. 用 SQL Fiddle 解題<a class="h-anchor" href="#1-%e7%94%a8-sql-fiddle-%e8%a7%a3%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>你的 <strong>SQL 版本會被限制在 5.6</strong>，你需要在許多好用函數都無法使用的狀態下完成這個題目</p>
</blockquote>
<p><strong>解題思路</strong>：因為 5.6 並沒有 row_number() 這個函數，所以我們要自己製作出來；另外多利用 subquery 的概念讓解答符合題目的輸出結果。</p>
<figure class="figure"><img src="https://deanlin.net/blog/202102/online-sql-playgrounds/images/online-sql-playgrounds-04.webp" width="1400" height="1098" alt="解答的原始碼在這裡喔！" loading="lazy" decoding="async">
  <figcaption><strong>解答的<a href="https://gist.github.com/dean9703111/1cd40fc06a6d78bb592cb83b1b2422ce">原始碼在這裡喔！</a></strong></figcaption>
</figure>

<h3 class="h-nomark" id="2-用-mysql-online-解題">2. 用 MySQL Online 解題<a class="h-anchor" href="#2-%e7%94%a8-mysql-online-%e8%a7%a3%e9%a1%8c" aria-label="這個段落的連結"></a></h3>
<blockquote>
<p>你的 <strong>SQL 版本為 8.0</strong>，你解題的重點便在於對新的函數是否熟悉</p>
</blockquote>
<p><strong>解題思路</strong>：MySQL 8.0 是一個重大更新，ROW_NUMBER 這個函數就能讓你以輕鬆的姿態搞定這個題目，如果你不熟悉可以<a href="https://www.begtut.com/mysql/mysql-row-number-function.html">參考這份文檔</a>。</p>
<figure class="figure"><img src="https://deanlin.net/blog/202102/online-sql-playgrounds/images/online-sql-playgrounds-05.webp" width="1400" height="859" alt="解答的原始碼在這裡喔！" loading="lazy" decoding="async">
  <figcaption><strong>解答的<a href="https://gist.github.com/dean9703111/b4bbcc0ff66d2544aeee18c89f9f63ad">原始碼在這裡喔！</a></strong></figcaption>
</figure>

<hr>
<blockquote class="pullquote"><p>我的解答僅供參考(尚未優化)，有興趣的朋友可以在底下留言分享你的做法喔～希望這篇文章能幫助臨時需要寫 SQL 的朋友</p>
</blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>0難度讓你在Server安裝設定MySQL</title>
      <link>https://deanlin.net/blog/202006/install-mysql-on-server/</link>
      <pubDate>Tue, 16 Jun 2020 14:28:52 +0800</pubDate>
      <guid>https://deanlin.net/blog/202006/install-mysql-on-server/</guid>
      <dc:creator>林鼎淵</dc:creator>
      <category>技術教學</category>
      <description>這是一篇只要複製貼上，注意小細節就能讓你輕鬆在ubuntu server設定好MySQL的文章</description>
      <media:content url="https://deanlin.net/blog/202006/install-mysql-on-server/images/install-mysql-on-server-og_hu_33e5876e48e9a7e5.jpg" medium="image" type="image/jpeg" width="1200" height="675" />
      <content:encoded><![CDATA[<figure class="figure"><img src="https://deanlin.net/blog/202006/install-mysql-on-server/images/install-mysql-on-server-01.webp" width="1200" height="618" alt="海豚好可愛" fetchpriority="high" decoding="async">
  <figcaption>海豚好可愛</figcaption>
</figure>

<h2 id="文章架構如下">文章架構如下：<a class="h-anchor" href="#%e6%96%87%e7%ab%a0%e6%9e%b6%e6%a7%8b%e5%a6%82%e4%b8%8b" aria-label="這個段落的連結"></a></h2>
<ol>
<li>安裝MySQL</li>
<li>設定MySQL可以讓外網連線</li>
<li>MySQL防火牆設定</li>
<li>建立新DB</li>
<li>新增/刪除使用者及權限</li>
<li>列出資料庫有權限的使用者列表</li>
<li>列出所有User權限表</li>
<li>修改MySQL root密碼</li>
<li>修改整體密碼規則強度</li>
</ol>
<h2 class="h-nomark" id="1-安裝mysql">1. 安裝MySQL<a class="h-anchor" href="#1-%e5%ae%89%e8%a3%9dmysql" aria-label="這個段落的連結"></a></h2>
<p>在安裝MySQL的時候會問你很多問題，如果你沒有特殊需求就都用y通過，密碼強度部分需要記住自己是怎麼設定的(因為不同密碼強度的規則差異很大，詳情可以看<a href="http://n.sfs.tw/content/index/13359">這篇</a>)</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt update
</span></span><span style="display:flex;"><span>sudo apt install mysql-server
</span></span><span style="display:flex;"><span>sudo mysql_secure_installation
</span></span></code></pre></div><p>安裝完成後你可以先進入MySQL看看</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo mysql -u root -p
</span></span></code></pre></div><h2 class="h-nomark" id="2-設定mysql可以讓外網連線">2. 設定MySQL可以讓外網連線<a class="h-anchor" href="#2-%e8%a8%ad%e5%ae%9amysql%e5%8f%af%e4%bb%a5%e8%ae%93%e5%a4%96%e7%b6%b2%e9%80%a3%e7%b7%9a" aria-label="這個段落的連結"></a></h2>
<p>如果你所架設的MySQL是允許外網或者某個特定ip做連線的，你就做以下調整</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
</span></span><span style="display:flex;"><span>bind-address <span style="color:#ff7b72;font-weight:bold">=</span> 0.0.0.0 //這個是指誰都可以連線的意思
</span></span><span style="display:flex;"><span>sudo su
</span></span><span style="display:flex;"><span>service mysql restart //設定完成後要重啟才會生效
</span></span></code></pre></div><h2 class="h-nomark" id="3-mysql防火牆設定">3. MySQL防火牆設定<a class="h-anchor" href="#3-mysql%e9%98%b2%e7%81%ab%e7%89%86%e8%a8%ad%e5%ae%9a" aria-label="這個段落的連結"></a></h2>
<p>以下為最基礎的防火牆設定，資料庫慣用的port為3306，22是給ssh登入使用的</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-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt-get install ufw
</span></span><span style="display:flex;"><span>sudo ufw enable
</span></span><span style="display:flex;"><span>sudo ufw allow <span style="color:#a5d6ff">3306</span>
</span></span><span style="display:flex;"><span>sudo ufw allow 22/tcp
</span></span><span style="display:flex;"><span>sudo ufw status
</span></span></code></pre></div><h2 class="h-nomark" id="4-建立新db">4. 建立新DB<a class="h-anchor" href="#4-%e5%bb%ba%e7%ab%8b%e6%96%b0db" aria-label="這個段落的連結"></a></h2>
<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-bash" data-lang="bash"><span style="display:flex;"><span>sudo mysql -u root -p
</span></span></code></pre></div><p>進入MySQL後你可以用指令的方式建立DB如下，如果你需要修改他默認編碼也能夠透過ALTER去做修正</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">DATABASE</span><span style="color:#6e7681"> </span>db_name;<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">DATABASE</span><span style="color:#6e7681"> </span>xxx_db<span style="color:#6e7681"> </span>CHARACTER<span style="color:#6e7681"> </span><span style="color:#ff7b72">SET</span><span style="color:#6e7681"> </span>utf8<span style="color:#6e7681"> </span><span style="color:#ff7b72">COLLATE</span><span style="color:#6e7681"> </span>utf8_bin;<span style="color:#6e7681">
</span></span></span></code></pre></div><h2 class="h-nomark" id="5-新增刪除使用者及權限">5. 新增/刪除使用者及權限<a class="h-anchor" href="#5-%e6%96%b0%e5%a2%9e%e5%88%aa%e9%99%a4%e4%bd%bf%e7%94%a8%e8%80%85%e5%8f%8a%e6%ac%8a%e9%99%90" aria-label="這個段落的連結"></a></h2>
<blockquote>
<p>新增mysql使用者及賦予權限</p>
</blockquote>
<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>use<span style="color:#6e7681"> </span>mysql;<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">//</span><span style="color:#f85149">進入這個資料庫</span><span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">create</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">user</span><span style="color:#6e7681"> </span><span style="color:#f85149">‘某某人’</span><span style="color:#ff7b72;font-weight:bold">@</span><span style="color:#f85149">‘哪個</span>ip可以(<span style="color:#ff7b72;font-weight:bold">%</span><span style="color:#f85149">為全部</span>)<span style="color:#f85149">’</span><span style="color:#6e7681"> </span>IDENTIFIED<span style="color:#6e7681"> </span><span style="color:#ff7b72">WITH</span><span style="color:#6e7681"> </span>mysql_native_password<span style="color:#6e7681"> </span><span style="color:#ff7b72">BY</span><span style="color:#6e7681"> </span><span style="color:#f85149">‘密碼’</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">CREATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">DATABASE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">`</span><span style="color:#f85149">資料庫名稱</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">grant</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">all</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">privileges</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">on</span><span style="color:#6e7681"> </span><span style="color:#f85149">資料庫名稱</span>(<span style="color:#f85149">如果沒填就是全部</span>).<span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">to</span><span style="color:#6e7681"> </span><span style="color:#f85149">‘某某人’</span><span style="color:#ff7b72;font-weight:bold">@</span><span style="color:#f85149">‘哪個</span>ip可以<span style="color:#f85149">’</span><span style="color:#6e7681"> </span>identified<span style="color:#6e7681"> </span><span style="color:#ff7b72">by</span><span style="color:#6e7681"> </span><span style="color:#f85149">‘密碼’</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><blockquote>
<p>刪除使用者及其權限</p>
</blockquote>
<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">REVOKE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">ALL</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">PRIVILEGES</span>,<span style="color:#6e7681"> </span><span style="color:#ff7b72">GRANT</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">OPTION</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">FROM</span><span style="color:#6e7681"> </span><span style="color:#f85149">‘某某人’</span><span style="color:#ff7b72;font-weight:bold">@</span><span style="color:#f85149">‘哪個</span>ip可以(<span style="color:#ff7b72;font-weight:bold">%</span><span style="color:#f85149">為全部</span>)<span style="color:#f85149">’</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">DROP</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">USER</span><span style="color:#6e7681"> </span><span style="color:#f85149">‘某某人’</span><span style="color:#ff7b72;font-weight:bold">@</span><span style="color:#f85149">‘哪個</span>ip可以(<span style="color:#ff7b72;font-weight:bold">%</span><span style="color:#f85149">為全部</span>)<span style="color:#f85149">’</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>flush<span style="color:#6e7681"> </span><span style="color:#ff7b72">privileges</span>;<span style="color:#6e7681"> </span><span style="color:#ff7b72;font-weight:bold">//</span><span style="color:#f85149">刷新權限</span><span style="color:#6e7681">
</span></span></span><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>information_schema.user_privileges;<span style="color:#ff7b72;font-weight:bold">//</span><span style="color:#f85149">確認該</span>User權限已被刪除<span style="color:#6e7681">
</span></span></span></code></pre></div><h2 class="h-nomark" id="6-列出資料庫有權限的使用者列表">6. 列出資料庫有權限的使用者列表<a class="h-anchor" href="#6-%e5%88%97%e5%87%ba%e8%b3%87%e6%96%99%e5%ba%ab%e6%9c%89%e6%ac%8a%e9%99%90%e7%9a%84%e4%bd%bf%e7%94%a8%e8%80%85%e5%88%97%e8%a1%a8" aria-label="這個段落的連結"></a></h2>
<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">user</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">from</span><span style="color:#6e7681"> </span>mysql.db<span style="color:#6e7681"> </span><span style="color:#ff7b72">where</span><span style="color:#6e7681"> </span>db<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#f85149">’資料庫名稱’</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><h2 class="h-nomark" id="7-列出所有user權限表">7. 列出所有User權限表<a class="h-anchor" href="#7-%e5%88%97%e5%87%ba%e6%89%80%e6%9c%89user%e6%ac%8a%e9%99%90%e8%a1%a8" aria-label="這個段落的連結"></a></h2>
<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>information_schema.user_privileges;<span style="color:#6e7681">
</span></span></span></code></pre></div><h2 class="h-nomark" id="8-修改mysql-root密碼">8. 修改mysql root密碼<a class="h-anchor" href="#8-%e4%bf%ae%e6%94%b9mysql-root%e5%af%86%e7%a2%bc" aria-label="這個段落的連結"></a></h2>
<p>請切記要修改root的密碼，避免駭客侵入你的伺服器後可以無所忌憚的讀取你的資料</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>use<span style="color:#6e7681"> </span>mysql;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">//</span><span style="color:#6e7681"> </span><span style="color:#f85149">先指定</span><span style="color:#6e7681"> </span>plugin<span style="color:#6e7681"> </span><span style="color:#f85149">為</span><span style="color:#6e7681"> </span>mysql_native_password<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">UPDATE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">user</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">SET</span><span style="color:#6e7681"> </span>plugin<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#f85149">’</span>mysql_native_password<span style="color:#f85149">’</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">WHERE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">User</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#f85149">’</span>root<span style="color:#f85149">’</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span><span style="color:#ff7b72">UPDATE</span><span style="color:#6e7681"> </span>mysql.<span style="color:#ff7b72">user</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">SET</span><span style="color:#6e7681"> </span>authentication_string<span style="color:#ff7b72;font-weight:bold">=</span>PASSWORD(<span style="color:#f85149">‘</span>password<span style="color:#f85149">’</span>)<span style="color:#6e7681"> </span><span style="color:#ff7b72">WHERE</span><span style="color:#6e7681"> </span><span style="color:#ff7b72">USER</span><span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#f85149">’</span>root<span style="color:#f85149">’</span>;<span style="color:#6e7681">
</span></span></span><span style="display:flex;"><span>FLUSH<span style="color:#6e7681"> </span><span style="color:#ff7b72">PRIVILEGES</span>;<span style="color:#6e7681">
</span></span></span></code></pre></div><h2 class="h-nomark" id="9-修改整體密碼規則強度">9. 修改整體密碼規則強度<a class="h-anchor" href="#9-%e4%bf%ae%e6%94%b9%e6%95%b4%e9%ab%94%e5%af%86%e7%a2%bc%e8%a6%8f%e5%89%87%e5%bc%b7%e5%ba%a6" aria-label="這個段落的連結"></a></h2>
<p>如果你在安裝時對於密碼強度的問題是隨便選的，你可以在登入MySQL後用這個指令做修正</p>
<pre tabindex="0"><code>set global validate_password_policy=0;
</code></pre><blockquote>
<p>密碼強度檢查等級，0/LOW、1/MEDIUM、2/STRONG</p>
<p>0 or LOW Length <br>1 or MEDIUM Length; numeric, lowercase/uppercase, and special characters <br>2 or STRONG Length; numeric, lowercase/uppercase, and special characters;</p>
<p>預設是1，即MEDIUM，所以剛開始設定的密碼必須符合長度，且必須含有數字，小寫或大寫字母，特殊字元。</p>
</blockquote>
<p>修改密碼長度限制</p>
<pre tabindex="0"><code>set global validate_password_length=6;
</code></pre><blockquote>
<p>千萬不要為了圖方便而設計複雜度太低的密碼</p>
</blockquote>
]]></content:encoded>
    </item>
  </channel>
</rss>
