<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sql-Features on Tarragon</title><link>https://tarrragon.github.io/blog/tags/sql-features/</link><description>Recent content in Sql-Features on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 19 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/sql-features/index.xml" rel="self" type="application/rss+xml"/><item><title>MySQL 8.0 Modern SQL：CTE / window function / JSON_TABLE 不是「終於跟上 PG」、是進入 SQL 工程深度的入場券</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/modern-sql-features/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/modern-sql-features/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/" data-link-title="MySQL" data-link-desc="高併發網路服務常用關聯式資料庫、Vitess / PlanetScale 分片生態、GitHub / Shopify / Facebook 規模驗證">MySQL&lt;/a> overview 的 implementation-layer deep article。Overview 已說明 MySQL 在 OLTP 譜系的定位、本文聚焦 &lt;em>8.0 modern SQL 特性&lt;/em> — 5 個關鍵能力 + 跟 PostgreSQL 對應特性的對比。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;p>「MySQL 是 SQL 簡單版」是個過時觀念。&lt;/p>
&lt;p>這個觀念的來源很合理：MySQL 5.x 時代沒 CTE、window function 要嗑 hack、recursive query 寫不出來、JSON 處理是字串 substring 拼接、複雜分析 query 只能丟去 PostgreSQL 或 Snowflake。整整 10 年 SQL 進階特性 MySQL 全缺、PostgreSQL 全有。&lt;/p>
&lt;p>MySQL 8.0（2018 推出）改變這件事。CTE / window function / lateral derived table / JSON_TABLE / hash join / atomic DDL / role-based authentication / common table expression 全部進來。&lt;strong>這不是「終於跟上 PG」、是 MySQL 第一次有資格進入 SQL 工程深度討論&lt;/strong>。但有 caveats：每個特性的 &lt;em>行為實現&lt;/em> 跟 PostgreSQL 對應特性都有 &lt;em>微妙差異&lt;/em>、不能假設 PG 經驗直接套用。&lt;/p>
&lt;p>對從 PostgreSQL 過來評估 MySQL 的讀者：本文是 &lt;em>特性對等驗證&lt;/em> — 哪些 8.0 特性真的可以 production 用、哪些是 marketing 但實作有 gap。對既有 MySQL 5.7 user：本文是 &lt;em>upgrade 5.7 → 8.0 的具體 ROI&lt;/em> — 從 SQL feature 角度看升級值不值得。&lt;/p>
&lt;h2 id="5-個關鍵特性--pg-對比">5 個關鍵特性 + PG 對比&lt;/h2>
&lt;h3 id="特性-1ctecommon-table-expression">特性 1：CTE（Common Table Expression）&lt;/h3>
&lt;p>MySQL 8.0 / PG 8.4+ 都支援。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sql" data-lang="sql">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="c1">-- MySQL 8.0 + PG 都 OK
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="k">WITH&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">order_summary&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">SUM&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">amount&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">AS&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">total&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">orders&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">created_at&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;2026-01-01&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="k">GROUP&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">BY&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">SELECT&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">name&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">total&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">users&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">JOIN&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">order_summary&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="k">ON&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">u&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">id&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">user_id&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="k">WHERE&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="n">os&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="n">total&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="o">&amp;gt;&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="mi">1000&lt;/span>&lt;span class="p">;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>行為差異&lt;/strong>：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/mysql/" data-link-title="MySQL" data-link-desc="高併發網路服務常用關聯式資料庫、Vitess / PlanetScale 分片生態、GitHub / Shopify / Facebook 規模驗證">MySQL</a> overview 的 implementation-layer deep article。Overview 已說明 MySQL 在 OLTP 譜系的定位、本文聚焦 <em>8.0 modern SQL 特性</em> — 5 個關鍵能力 + 跟 PostgreSQL 對應特性的對比。</p></blockquote>
<hr>
<p>「MySQL 是 SQL 簡單版」是個過時觀念。</p>
<p>這個觀念的來源很合理：MySQL 5.x 時代沒 CTE、window function 要嗑 hack、recursive query 寫不出來、JSON 處理是字串 substring 拼接、複雜分析 query 只能丟去 PostgreSQL 或 Snowflake。整整 10 年 SQL 進階特性 MySQL 全缺、PostgreSQL 全有。</p>
<p>MySQL 8.0（2018 推出）改變這件事。CTE / window function / lateral derived table / JSON_TABLE / hash join / atomic DDL / role-based authentication / common table expression 全部進來。<strong>這不是「終於跟上 PG」、是 MySQL 第一次有資格進入 SQL 工程深度討論</strong>。但有 caveats：每個特性的 <em>行為實現</em> 跟 PostgreSQL 對應特性都有 <em>微妙差異</em>、不能假設 PG 經驗直接套用。</p>
<p>對從 PostgreSQL 過來評估 MySQL 的讀者：本文是 <em>特性對等驗證</em> — 哪些 8.0 特性真的可以 production 用、哪些是 marketing 但實作有 gap。對既有 MySQL 5.7 user：本文是 <em>upgrade 5.7 → 8.0 的具體 ROI</em> — 從 SQL feature 角度看升級值不值得。</p>
<h2 id="5-個關鍵特性--pg-對比">5 個關鍵特性 + PG 對比</h2>
<h3 id="特性-1ctecommon-table-expression">特性 1：CTE（Common Table Expression）</h3>
<p>MySQL 8.0 / PG 8.4+ 都支援。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1">-- MySQL 8.0 + PG 都 OK
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="c1"></span><span class="k">WITH</span><span class="w"> </span><span class="n">order_summary</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="k">SELECT</span><span class="w"> </span><span class="n">user_id</span><span class="p">,</span><span class="w"> </span><span class="k">SUM</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">total</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="k">FROM</span><span class="w"> </span><span class="n">orders</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="k">WHERE</span><span class="w"> </span><span class="n">created_at</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="s1">&#39;2026-01-01&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="k">GROUP</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">user_id</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">os</span><span class="p">.</span><span class="n">total</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="n">u</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="n">order_summary</span><span class="w"> </span><span class="n">os</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="n">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">os</span><span class="p">.</span><span class="n">user_id</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">os</span><span class="p">.</span><span class="n">total</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">1000</span><span class="p">;</span></span></span></code></pre></div><p><strong>行為差異</strong>：</p>
<ul>
<li><strong>MySQL 8.0</strong>：CTE <em>不 materialize 為預設</em>、optimizer 把 CTE 視為 <em>inlined subquery</em>、CTE 引用兩次以上會 <em>重複計算</em></li>
<li><strong>PostgreSQL（&lt; 12）</strong>：CTE <em>fence by default</em>（materialize barrier）、optimizer 不 push predicate 進 CTE</li>
<li><strong>PostgreSQL（12+）</strong>：CTE 行為跟 MySQL 接近、有 <code>MATERIALIZED</code> / <code>NOT MATERIALIZED</code> keyword 明示</li>
</ul>
<p>對 PG 12+ user：可以套 MySQL 經驗。對 PG 11 以下 user：CTE 行為跟 MySQL 不一樣、要重看 query plan。</p>
<p><strong>Recursive CTE</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">WITH</span><span class="w"> </span><span class="k">RECURSIVE</span><span class="w"> </span><span class="n">org_chart</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="k">SELECT</span><span class="w"> </span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">manager_id</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">depth</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="k">FROM</span><span class="w"> </span><span class="n">employees</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">manager_id</span><span class="w"> </span><span class="k">IS</span><span class="w"> </span><span class="k">NULL</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="k">UNION</span><span class="w"> </span><span class="k">ALL</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="k">SELECT</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="n">manager_id</span><span class="p">,</span><span class="w"> </span><span class="n">oc</span><span class="p">.</span><span class="n">depth</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="k">FROM</span><span class="w"> </span><span class="n">employees</span><span class="w"> </span><span class="n">e</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="n">org_chart</span><span class="w"> </span><span class="n">oc</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">e</span><span class="p">.</span><span class="n">manager_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">oc</span><span class="p">.</span><span class="n">id</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">org_chart</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">depth</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span></span></span></code></pre></div><p>兩家都支援、但 MySQL 8.0 有 <em>深度上限</em>（<code>cte_max_recursion_depth=1000</code>、預設 1000、PG 預設 unlimited）。複雜 hierarchical query（深度 &gt; 1000）MySQL 需要顯式提高 limit。</p>
<h3 id="特性-2window-function">特性 2：Window Function</h3>
<p>MySQL 8.0 / PG 8.4+ 都支援、語法同 SQL standard。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">    </span><span class="n">order_id</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="n">user_id</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="n">amount</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="k">SUM</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span><span class="w"> </span><span class="n">OVER</span><span class="w"> </span><span class="p">(</span><span class="n">PARTITION</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">user_id</span><span class="w"> </span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">created_at</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">running_total</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="n">RANK</span><span class="p">()</span><span class="w"> </span><span class="n">OVER</span><span class="w"> </span><span class="p">(</span><span class="n">PARTITION</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">user_id</span><span class="w"> </span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="k">DESC</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">rank_in_user</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">orders</span><span class="p">;</span></span></span></code></pre></div><p><strong>行為差異</strong>：</p>
<ul>
<li><strong>執行 plan</strong>：MySQL 8.0 用 <em>window iterator</em>、單 partition 內 sort、外加 in-memory window buffer。PostgreSQL 有更成熟的 <em>WindowAgg node</em>、複雜 frame spec 處理更好</li>
<li><strong>Frame spec 支援度</strong>：兩家都支援 ROWS / RANGE / GROUPS、但 <em>GROUPS frame</em> MySQL 是 8.0.16+ 才補進、PG 11+ 才補</li>
<li><strong>大資料量 spill behavior</strong>：MySQL window function 超過 <code>sort_buffer_size</code>（預設 256K）會 spill 到 disk、Performance 雪崩。PG 用 <code>work_mem</code>（預設 4MB）、寬裕些但也會 spill</li>
</ul>
<p>對長期用 PG window function 寫複雜 reporting query 的 user：MySQL 8.0 可以做、但 <em>效能 tune</em> 工作量大、不是 drop-in。</p>
<h3 id="特性-3json_tablepg-主要賣點對比">特性 3：JSON_TABLE（PG 主要賣點對比）</h3>
<p>這是 user 點到的對比重點。</p>
<p><strong>MySQL 8.0 的 JSON_TABLE</strong>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">t</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="p">.</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">j</span><span class="p">.</span><span class="n">price</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">products</span><span class="w"> </span><span class="n">t</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">     </span><span class="n">JSON_TABLE</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">         </span><span class="n">t</span><span class="p">.</span><span class="n">metadata</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">         </span><span class="s1">&#39;$.variants[*]&#39;</span><span class="w"> </span><span class="n">COLUMNS</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">             </span><span class="n">name</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span><span class="w"> </span><span class="n">PATH</span><span class="w"> </span><span class="s1">&#39;$.name&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">             </span><span class="n">price</span><span class="w"> </span><span class="nb">DECIMAL</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="n">PATH</span><span class="w"> </span><span class="s1">&#39;$.price&#39;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">         </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">     </span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">j</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">t</span><span class="p">.</span><span class="n">category</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;shoes&#39;</span><span class="p">;</span></span></span></code></pre></div><p>JSON_TABLE 把 JSON document 內的 array element 展開成 <em>relational rows</em>、然後可以 JOIN / WHERE / GROUP BY。SQL:2016 standard 規範。</p>
<p><strong>PostgreSQL 對應</strong>：</p>
<p>PG 17+ 有 <code>JSON_TABLE</code>（SQL:2016 standard、跟 MySQL 同語法）、但歷史上 PG user 用兩條不同路線：</p>
<ol>
<li>
<p><strong>JSONB operator</strong>（PG 9.4+）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">metadata</span><span class="o">-&gt;</span><span class="s1">&#39;variants&#39;</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">variants</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">products</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">WHERE</span><span class="w"> </span><span class="n">metadata</span><span class="w"> </span><span class="o">@&gt;</span><span class="w"> </span><span class="s1">&#39;{&#34;category&#34;: &#34;shoes&#34;}&#39;</span><span class="p">;</span></span></span></code></pre></div></li>
<li>
<p><strong>jsonb_path_query</strong>（PG 12+）：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">t</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">v</span><span class="p">.</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">v</span><span class="p">.</span><span class="n">price</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">products</span><span class="w"> </span><span class="n">t</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">     </span><span class="n">jsonb_path_query</span><span class="p">(</span><span class="n">t</span><span class="p">.</span><span class="n">metadata</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;$.variants[*]&#39;</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">v</span><span class="p">;</span></span></span></code></pre></div></li>
</ol>
<p><strong>核心差異</strong>：</p>
<table>
  <thead>
      <tr>
          <th>維度</th>
          <th>MySQL JSON_TABLE</th>
          <th>PG JSONB operator</th>
          <th>PG jsonb_path_query</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Index</td>
          <td>必須對 JSON column 建 <em>generated column + 一般 index</em>、不能直接 GIN index JSON path</td>
          <td><strong>GIN index 直接 over JSONB</strong>（業界唯一）</td>
          <td>可以走 GIN expression index</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td>JSON column = LONGTEXT 包裝</td>
          <td>JSONB = binary、壓縮、index 友善</td>
          <td>同左</td>
      </tr>
      <tr>
          <td>Query 效率（複雜 path）</td>
          <td>中等（需要 generated column 加速）</td>
          <td>高（GIN index 直接）</td>
          <td>高</td>
      </tr>
      <tr>
          <td>SQL standard 對齊</td>
          <td>高（JSON_TABLE 是 standard）</td>
          <td>低（JSONB operator 是 PG 專有）</td>
          <td>中（jsonpath 是 standard）</td>
      </tr>
      <tr>
          <td>大 JSON（&gt; 1 MB）</td>
          <td>LONGTEXT 仍可、但 query 慢</td>
          <td>JSONB 壓縮 + 部分 read</td>
          <td>同左</td>
      </tr>
  </tbody>
</table>
<p><strong>選型結論</strong>：</p>
<ul>
<li><strong>MySQL 是 JSON-storage 角色</strong>（document 順手存進關聯 DB）：JSON_TABLE 夠用、配 generated column + index、production-ready</li>
<li><strong>MySQL 是 document-heavy workload</strong>（大量 JSON-driven query / 複雜 path / 高 selectivity）：PG JSONB GIN index 仍是 <em>clearly winner</em>、或直接用 MongoDB</li>
<li><strong>MySQL 8.0 JSON 不是 PG JSONB 替代</strong>：JSON_TABLE 是 <em>SQL standard 對齊</em>、好 portable、但 <em>index 跟 storage 仍弱</em></li>
</ul>
<p>對「JSON 是 PG 主要賣點」的判斷：JSONB binary storage + GIN index 是 PG 在 JSON workload 的 <em>結構性優勢</em>、MySQL 8.0 補了 SQL_TABLE 但 <em>index 那層沒補</em>。8.0 後 JSON 議題 <em>不是 deal-breaker for MySQL</em>（不像 5.7 時代直接 disqualify）、但仍不是 MySQL 主場。</p>
<h3 id="特性-4lateral-derived-table">特性 4：Lateral Derived Table</h3>
<p>MySQL 8.0.14+ / PG 9.3+ 都支援。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- 對每個 user、找他最近 5 個 order
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">recent</span><span class="p">.</span><span class="o">*</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="n">u</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="k">LEFT</span><span class="w"> </span><span class="k">JOIN</span><span class="w"> </span><span class="k">LATERAL</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="k">SELECT</span><span class="w"> </span><span class="n">order_id</span><span class="p">,</span><span class="w"> </span><span class="n">amount</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">    </span><span class="k">FROM</span><span class="w"> </span><span class="n">orders</span><span class="w"> </span><span class="n">o</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span><span class="k">WHERE</span><span class="w"> </span><span class="n">o</span><span class="p">.</span><span class="n">user_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">u</span><span class="p">.</span><span class="n">id</span><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="w">    </span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">created_at</span><span class="w"> </span><span class="k">DESC</span><span class="w"> </span><span class="k">LIMIT</span><span class="w"> </span><span class="mi">5</span><span class="w">
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="w"></span><span class="p">)</span><span class="w"> </span><span class="n">recent</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="k">true</span><span class="p">;</span></span></span></code></pre></div><p>Lateral 讓 subquery 可以 <em>引用外部 reference column</em>（<code>u.id</code>）、不可能用 plain subquery 寫出來。</p>
<p><strong>行為差異</strong>：</p>
<ul>
<li>MySQL 8.0：lateral 後加、optimizer plan 仍在演進、複雜 lateral query 可能 plan 次優</li>
<li>PostgreSQL：lateral 早就成熟、plan 跟 join 直接 fuse、效率高</li>
</ul>
<p>對 PG-experienced 使用 lateral 寫 reporting query 的 user：MySQL 8.0 可以、但有時候要 hint optimizer 達到最佳 plan。</p>
<h3 id="特性-5hash-join">特性 5：Hash Join</h3>
<p>MySQL 8.0.18+ / PG 早已有。</p>
<p><strong>MySQL 8.0 之前</strong>：只有 <em>nested loop join</em>、大表 JOIN 完全失控（n × m row scan）。8.0.18 加 hash join、optimizer 在預估 row count 大時自動切。</p>
<p><strong>注意</strong>：MySQL 8.0 hash join 預設 <em>不對所有 join 開</em>、只在 <code>optimizer_switch='hash_join=on'</code> 且 join condition 是 <em>equality on indexed column</em> 時觸發。常見錯估：複雜 join 條件不觸發 hash join、optimizer fallback nested loop、query 永遠跑不完。</p>
<p><strong>PG 對應</strong>：PG 一直有 hash join、optimizer 預設 cover 廣、且有 <em>parallel hash join</em>（PG 11+）大表 JOIN 並行加速。</p>
<p>MySQL hash join 是 <em>補洞</em>、不是 <em>並肩特性</em>。複雜 OLAP query MySQL 仍弱於 PG。</p>
<h2 id="其他-80-特性一句話帶過">其他 8.0 特性（一句話帶過）</h2>
<ul>
<li><strong>Atomic DDL</strong>：CREATE TABLE / DROP / ALTER 變 transactional、crash recovery 不會留 orphan table（PG 早就 atomic）</li>
<li><strong>Role-based authentication</strong>：role 取代 group-level grant、user 可繼承 role（PG 早就 role 系統）</li>
<li><strong>CHECK constraint enforcement</strong>：5.7 可寫但不執行、8.0 真的 enforce（PG 一直執行）</li>
<li><strong>invisible index</strong>：建 index 但 optimizer 暫不用、適合 staging query plan 測試（PG 沒原生對應）</li>
<li><strong>Resource Group</strong>：query 跑時可分配 CPU thread 給特定 user group（PG 沒原生對應）</li>
<li><strong>Generated column</strong>：MySQL 5.7 已有、8.0 強化、可作為 JSON path 加速的 workaround</li>
</ul>
<h2 id="配置-step-by-step從-57--80-sql-feature-升級">配置 step-by-step（從 5.7 → 8.0 SQL feature 升級）</h2>
<p>如果已經是 8.0、所有特性都可以用、不必額外配置。如果是 5.7 → 8.0、需要：</p>
<ol>
<li><strong><code>character_set_server=utf8mb4</code></strong>：8.0 預設 utf8mb4（5.7 預設 latin1）、character set 不一致導致 query 行為微差</li>
<li><strong><code>default_authentication_plugin=mysql_native_password</code></strong>：8.0 預設 caching_sha2_password、舊 client 連不上、cluster upgrade 期間用 native_password 保兼容</li>
<li><strong><code>optimizer_switch='hash_join=on'</code></strong>：確認 hash join 啟用、預設應該已 ON</li>
<li><strong><code>cte_max_recursion_depth=10000</code></strong>：複雜 recursive CTE 需要時提高</li>
<li><strong>重新 review 所有 ORM-generated SQL</strong>：8.0 keywords 變多（WINDOW、RANK、LATERAL 等變成 reserved word）、5.7 識別碼可能變 syntax error</li>
</ol>
<h2 id="5-個-production-踩雷">5 個 Production 踩雷</h2>
<h3 id="1-cte-引用兩次--跑兩次">1. CTE 引用兩次 = 跑兩次</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">WITH</span><span class="w"> </span><span class="n">expensive</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="p">(</span><span class="k">SELECT</span><span class="w"> </span><span class="p">...</span><span class="w"> </span><span class="n">heavy</span><span class="w"> </span><span class="n">aggregation</span><span class="w"> </span><span class="p">...)</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">expensive</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="p">...</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">UNION</span><span class="w"> </span><span class="k">ALL</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">expensive</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">other_condition</span><span class="p">;</span></span></span></code></pre></div><p>預期 CTE 跑一次、實際 MySQL 跑兩次。Query 時間 doubled。</p>
<p>修法：</p>
<ul>
<li>把 CTE 結果先 INSERT 進 <em>temporary table</em>、SELECT 兩次走 temp table（手動 materialize）</li>
<li>或 PG 用 <code>MATERIALIZED</code> keyword（MySQL 沒對應 hint、要手動 temp table）</li>
</ul>
<h3 id="2-window-function-大-partition-spill-到-disk">2. Window function 大 partition spill 到 disk</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">order_id</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">       </span><span class="k">SUM</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span><span class="w"> </span><span class="n">OVER</span><span class="w"> </span><span class="p">(</span><span class="n">PARTITION</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">user_id</span><span class="w"> </span><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">created_at</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">orders</span><span class="p">;</span><span class="w">  </span><span class="c1">-- 1 億 row</span></span></span></code></pre></div><p><code>sort_buffer_size=256K</code> 預設、單 partition &gt; 256K row 開始 spill disk、執行從秒級變分鐘級。</p>
<p>修法：</p>
<ul>
<li>提高 <code>sort_buffer_size</code>（per-connection、不要設太大、connection × buffer 會吃 RAM）</li>
<li>加 INDEX 包含 <code>user_id, created_at</code>、optimizer 可直接用 sorted index、不必額外 sort</li>
</ul>
<h3 id="3-json_table-跟-generated-column-取捨錯誤">3. JSON_TABLE 跟 generated column 取捨錯誤</h3>
<p>直接 JSON_TABLE on every query：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">products</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="n">JSON_TABLE</span><span class="p">(</span><span class="n">metadata</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;$.variants[*]&#39;</span><span class="w"> </span><span class="n">COLUMNS</span><span class="w"> </span><span class="p">(...));</span></span></span></code></pre></div><p>每次 query 跑 JSON parse、無 index 加速、大表 query 慢。</p>
<p>修法：</p>
<ul>
<li>
<p>對 <em>常 query 的 JSON path</em> 建 generated column：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">ALTER</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">products</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">ADD</span><span class="w"> </span><span class="k">COLUMN</span><span class="w"> </span><span class="n">category</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">GENERATED</span><span class="w"> </span><span class="n">ALWAYS</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="p">(</span><span class="n">JSON_UNQUOTE</span><span class="p">(</span><span class="n">metadata</span><span class="o">-&gt;</span><span class="s1">&#39;$.category&#39;</span><span class="p">))</span><span class="w"> </span><span class="n">STORED</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="k">ADD</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_category</span><span class="w"> </span><span class="p">(</span><span class="n">category</span><span class="p">);</span></span></span></code></pre></div></li>
<li>
<p>JSON_TABLE 用於 <em>ad-hoc query</em>、不要當熱 path</p>
</li>
<li>
<p>跟 PG JSONB GIN 對比：PG 不必預先建 generated column、GIN index 直接 over JSONB</p>
</li>
</ul>
<h3 id="4-hash-join-沒觸發--optimizer-預估錯-row-count">4. Hash join 沒觸發 — Optimizer 預估錯 row count</h3>
<p>JOIN 大表預期 hash join、實際 MySQL 跑 nested loop、query 跑不完。常見原因：</p>
<ul>
<li>Table statistics 過時（沒跑 <code>ANALYZE TABLE</code>）</li>
<li>Join condition 不是 pure equality（<code>a.id = b.id + 1</code> 等）</li>
<li>一邊有 LIMIT、optimizer 估 small set、選 nested loop</li>
</ul>
<p>修法：</p>
<ul>
<li>跑 <code>ANALYZE TABLE</code> 更新 statistics</li>
<li>用 <code>EXPLAIN ANALYZE</code> 看實際 row count vs 估計</li>
<li>用 <code>optimizer_hint</code>（如 <code>/*+ HASH_JOIN(t1 t2) */</code>）強制</li>
</ul>
<h3 id="5-recursive-cte-深度上限--production-query-突然-fail">5. Recursive CTE 深度上限 — Production query 突然 fail</h3>
<p><code>cte_max_recursion_depth=1000</code> 預設、organization hierarchy / tree query 超過 1000 層直接 fail（<code>ER_CTE_MAX_RECURSION_DEPTH_EXCEEDED</code>）。</p>
<p>修法：</p>
<ul>
<li>評估真實 hierarchy 深度、設 <code>cte_max_recursion_depth=10000</code> 或更高</li>
<li>或 query 加 <code>WHERE depth &lt; N</code> 提前停（不依賴 implicit limit）</li>
<li>對極大 hierarchy（社群 follow graph 等）改用 <em>graph DB</em>（Neo4j）— MySQL recursive CTE 不是 graph workload 主場</li>
</ul>
<h2 id="mysql-80-vs-pg-sql-特性-cross-reference">MySQL 8.0 vs PG SQL 特性 cross-reference</h2>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>MySQL 8.0</th>
          <th>PostgreSQL</th>
          <th>差異</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CTE</td>
          <td>8.0+</td>
          <td>8.4+</td>
          <td>PG 2009 即支援、MySQL 2018 才支援、約晚 9 年</td>
      </tr>
      <tr>
          <td>Recursive CTE</td>
          <td>8.0+（depth 限）</td>
          <td>8.4+（unlimited）</td>
          <td>PG 無深度上限</td>
      </tr>
      <tr>
          <td>Window function</td>
          <td>8.0+</td>
          <td>8.4+</td>
          <td>Frame spec 兩家略不同（GROUPS frame 推出時點）</td>
      </tr>
      <tr>
          <td>Lateral</td>
          <td>8.0.14+</td>
          <td>9.3+</td>
          <td>PG plan 較成熟</td>
      </tr>
      <tr>
          <td>JSON_TABLE</td>
          <td>8.0+</td>
          <td>17+</td>
          <td>MySQL 早 6 年（SQL:2016 standard）</td>
      </tr>
      <tr>
          <td>JSONB index</td>
          <td>無原生</td>
          <td>GIN index over JSONB</td>
          <td><strong>PG 結構優勢</strong></td>
      </tr>
      <tr>
          <td>Hash join</td>
          <td>8.0.18+</td>
          <td>早</td>
          <td>PG parallel hash join</td>
      </tr>
      <tr>
          <td>Atomic DDL</td>
          <td>8.0+</td>
          <td>早</td>
          <td>PG 一直 atomic</td>
      </tr>
      <tr>
          <td>Common keyword</td>
          <td>補齊</td>
          <td>完整</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Role-based auth</td>
          <td>8.0+</td>
          <td>早</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Materialized view</td>
          <td>無原生</td>
          <td>9.3+</td>
          <td><strong>PG 結構優勢</strong>（MySQL 用 trigger / scheduled refresh 模擬）</td>
      </tr>
      <tr>
          <td>Partial index</td>
          <td>無</td>
          <td>早</td>
          <td><strong>PG 結構優勢</strong></td>
      </tr>
      <tr>
          <td>Expression index</td>
          <td>8.0.13+</td>
          <td>早</td>
          <td>MySQL 後加</td>
      </tr>
      <tr>
          <td>Full-text search</td>
          <td>內建（InnoDB 5.6+）</td>
          <td>內建（tsvector）</td>
          <td>PG full-text 更成熟</td>
      </tr>
      <tr>
          <td>Foreign data wrapper</td>
          <td>無原生</td>
          <td>早（FDW）</td>
          <td><strong>PG 結構優勢</strong></td>
      </tr>
  </tbody>
</table>
<p>8.0 補了 <em>語法層</em> 大部分缺漏、<em>storage / index / extensibility 層</em> 仍是 PG 結構優勢。對「先選 SQL 工程深度」的 org、PG 仍領先；對「先選 ecosystem / replication / sharding」的 org、MySQL 已不是 disqualifier。</p>
<h2 id="跟其他模組整合">跟其他模組整合</h2>
<h3 id="跟-innodb-tuning">跟 InnoDB Tuning</h3>
<p>JSON column 在 InnoDB 是 LONGTEXT 包裝、大 JSON 進 off-page storage（<code>innodb_default_row_format=DYNAMIC</code> 才行、Antelope format 不支援）。Buffer pool 對 LONGTEXT 較不友善、大 JSON workload 可能要更大 buffer pool。詳見 <a href="/blog/backend/01-database/vendors/mysql/innodb-tuning/" data-link-title="MySQL InnoDB Tuning：為什麼一個 100 GB DB 在 64 GB RAM server 上 query 慢 5 倍" data-link-desc="InnoDB 是 MySQL 預設 storage engine、預設值給 256 MB buffer pool（早期 default）。本文從一個常見痛點開場（DB &gt; RAM 但 server 仍 swap）、走 4 個 critical knob（buffer pool / redo log / flush method / IO capacity）、各自如何影響讀寫吞吐、配置 step-by-step、5 production 踩雷（buffer pool warm-up / log file 大小 / 設 sync_binlog=0 換速度 / IO scheduler / undo log 膨脹）、跟 SSD / NVMe / EBS 的 IO 假設">InnoDB Tuning</a>。</p>
<h3 id="跟-query-optimization">跟 Query Optimization</h3>
<p>8.0 新 hash join + lateral derived 讓 <em>EXPLAIN ANALYZE</em> 結果更複雜。優化複雜 query 需要熟 <em>新 plan node 類型</em>。詳見 <em>Query Optimization deep dive</em> 篇（待寫）。</p>
<h3 id="跟-online-schema-change">跟 Online Schema Change</h3>
<p>JSON column 跟 generated column 的 schema change 走 gh-ost / pt-osc 沒問題、但 JSON 大表 ALTER 速度比一般 column 慢（每 row 重 serialize）。詳見 <a href="/blog/backend/01-database/vendors/mysql/online-schema-change-tools/" data-link-title="MySQL Online Schema Change：gh-ost 跟 pt-online-schema-change 兩條完全不同的 ghost table 路徑" data-link-desc="MySQL ALTER TABLE 可能鎖整張表，production 需要 online schema change 流程。gh-ost（GitHub）跟 pt-online-schema-change（Percona）都用 ghost table 解決、但底層機制完全不同：pt-osc 用 trigger 同步、gh-ost 用 binlog stream 同步。本文走兩工具機制對照表 → trigger vs binlog 各自取捨 → 配置 step-by-step → 5 production 踩雷（trigger overhead / binlog 延遲 / FK constraint / hot trigger lock / 切換瞬間 deadlock）→ 何時用哪一個">Online Schema Change Tools</a>。</p>
<h3 id="跟-replication">跟 Replication</h3>
<p>Window function / CTE / JSON_TABLE 的 query <em>結果</em> replicate（row-level binlog 紀錄結果）、不 replicate <em>query 本身</em>。所以 replica apply 不會重新跑 window function、效率 OK。詳見 <a href="/blog/backend/01-database/vendors/mysql/replication-topology/" data-link-title="MySQL Replication Topology：async / semi-sync / GTID 不是三選一、是三個 trade-off 軸的疊加" data-link-desc="MySQL replication 不是「選 async 還是 semi-sync」、是 *durability / latency / consistency* 三個 trade-off 軸的疊加；GTID 是跨 mode 的 infrastructure layer、不是第三種 mode。本文走 3 軸取捨模型 → async / semi-sync 行為對比 → GTID 替代 binlog-position 的好處 → 配置 step-by-step → 5 production 踩雷（lag 暴衝 / semi-sync 退回 async / GTID gap / Loss-Less semi-sync 真的 loss-less / chained replication 雪崩）→ 跟 Aurora MySQL / Vitess / ProxySQL / Orchestrator 整合">Replication Topology</a>。</p>
<h2 id="何時-sql-特性是-mysql-選型-driver">何時 SQL 特性是 MySQL 選型 driver</h2>
<ul>
<li><strong>想要 SQL standard 對齊跨 vendor portable</strong>：MySQL 8.0 JSON_TABLE / window 都對齊 standard、PG 部分能力（JSONB operator）是 PG-only、portability MySQL 略好</li>
<li><strong>JSON workload &lt; 20% query</strong>：MySQL 8.0 + generated column 夠用、不必為 JSON 換 PG</li>
<li><strong>JSON workload &gt; 50% query + 複雜 path / aggregation</strong>：PG JSONB GIN 仍 winner、考慮 PG 或 MongoDB</li>
<li><strong>需要 materialized view / FDW / partial index</strong>：PG 仍領先、不要因為 SQL feature parity 假設 MySQL 全 cover</li>
<li><strong>既有 MySQL 投資 + SQL 工程深度上升</strong>：升 8.0 + 訓練團隊用新特性、不是換 vendor</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li><a href="/blog/backend/01-database/vendors/mysql/" data-link-title="MySQL" data-link-desc="高併發網路服務常用關聯式資料庫、Vitess / PlanetScale 分片生態、GitHub / Shopify / Facebook 規模驗證">MySQL vendor overview</a></li>
<li><a href="/blog/backend/01-database/vendors/mysql/innodb-tuning/" data-link-title="MySQL InnoDB Tuning：為什麼一個 100 GB DB 在 64 GB RAM server 上 query 慢 5 倍" data-link-desc="InnoDB 是 MySQL 預設 storage engine、預設值給 256 MB buffer pool（早期 default）。本文從一個常見痛點開場（DB &gt; RAM 但 server 仍 swap）、走 4 個 critical knob（buffer pool / redo log / flush method / IO capacity）、各自如何影響讀寫吞吐、配置 step-by-step、5 production 踩雷（buffer pool warm-up / log file 大小 / 設 sync_binlog=0 換速度 / IO scheduler / undo log 膨脹）、跟 SSD / NVMe / EBS 的 IO 假設">InnoDB Tuning</a>（JSON column 對 buffer pool 影響）</li>
<li><a href="/blog/backend/01-database/vendors/mysql/online-schema-change-tools/" data-link-title="MySQL Online Schema Change：gh-ost 跟 pt-online-schema-change 兩條完全不同的 ghost table 路徑" data-link-desc="MySQL ALTER TABLE 可能鎖整張表，production 需要 online schema change 流程。gh-ost（GitHub）跟 pt-online-schema-change（Percona）都用 ghost table 解決、但底層機制完全不同：pt-osc 用 trigger 同步、gh-ost 用 binlog stream 同步。本文走兩工具機制對照表 → trigger vs binlog 各自取捨 → 配置 step-by-step → 5 production 踩雷（trigger overhead / binlog 延遲 / FK constraint / hot trigger lock / 切換瞬間 deadlock）→ 何時用哪一個">Online Schema Change Tools</a>（JSON column 大表 ALTER）</li>
<li><a href="/blog/backend/01-database/vendors/mysql/replication-topology/" data-link-title="MySQL Replication Topology：async / semi-sync / GTID 不是三選一、是三個 trade-off 軸的疊加" data-link-desc="MySQL replication 不是「選 async 還是 semi-sync」、是 *durability / latency / consistency* 三個 trade-off 軸的疊加；GTID 是跨 mode 的 infrastructure layer、不是第三種 mode。本文走 3 軸取捨模型 → async / semi-sync 行為對比 → GTID 替代 binlog-position 的好處 → 配置 step-by-step → 5 production 踩雷（lag 暴衝 / semi-sync 退回 async / GTID gap / Loss-Less semi-sync 真的 loss-less / chained replication 雪崩）→ 跟 Aurora MySQL / Vitess / ProxySQL / Orchestrator 整合">Replication Topology</a>（ROW-format binlog 對 window function）</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/sql-features-baseline/" data-link-title="PostgreSQL SQL Features：PG 早就有的、MySQL 8.0 才補的、PG 仍領先的" data-link-desc="PG 在 SQL features 上長期領先 MySQL — CTE / window function / lateral / partial index / FTS / JSONB / GIN index / materialized view 在 PG 早 5-15 年。MySQL 8.0（2018）補多數但 *index / storage / extension* 層仍是 PG 結構優勢。本文整理 PG 早期就有的特性、MySQL 8.0 補的差異、PG 仍領先的、跟 MySQL modern-sql-features sibling 反向視角">PostgreSQL SQL Features Baseline</a>（PG 反向視角、哪些特性 PG 早 5-15 年、MySQL 8.0 補齊後 PG 仍領先）</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/jsonb-deep-dive/" data-link-title="PostgreSQL JSONB Deep Dive：Binary Storage &#43; GIN Index 為什麼是結構性優勢" data-link-desc="PG JSONB（9.4&#43;）是 *binary 儲存的 JSON*、可直接 GIN index、是 PG 在 JSON workload 的結構性優勢、跟 MongoDB / MySQL 8.0 JSON_TABLE 比仍領先。本文走 JSON vs JSONB 差異、GIN index 機制（jsonb_ops vs jsonb_path_ops）、operator &#43; path query、partial JSONB indexing、5 production 踩雷（大 JSONB 跟 TOAST / nested update / index 選錯 op class / jsonb_path_query 跟 jsonb_path_exists 行為差 / partial index 條件搞錯）、何時用 JSONB vs 拆 column">PostgreSQL JSONB Deep Dive</a>（PG sibling、binary storage + GIN index 跟 MySQL JSON_TABLE 對比）</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL vendor page</a>（JSON / SQL feature 對比 source）</li>
<li><a href="/blog/backend/01-database/vendors/mongodb/" data-link-title="MongoDB" data-link-desc="Document database 代表、Atlas managed、跨雲可用、許多大規模平台從 MongoDB 起家">MongoDB vendor page</a>（document-heavy workload 替代）</li>
<li>官方：<a href="https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html">MySQL 8.0 What&rsquo;s New</a></li>
</ul>
]]></content:encoded></item><item><title>PostgreSQL SQL Features：PG 早就有的、MySQL 8.0 才補的、PG 仍領先的</title><link>https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/sql-features-baseline/</link><pubDate>Tue, 19 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/sql-features-baseline/</guid><description>&lt;blockquote>
&lt;p>本文是 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL&lt;/a> overview 的 implementation-layer deep article。Overview 已說明 PG 在 OLTP 譜系的定位、本文聚焦 &lt;em>SQL features baseline&lt;/em> — PG 早期就有的、MySQL 8.0 才補的、PG 仍領先的、給從 MySQL 評估 PG 的讀者 reference。&lt;/p>&lt;/blockquote>
&lt;hr>
&lt;h2 id="pg-sql-工程深度的歷史錨點">PG SQL 工程深度的歷史錨點&lt;/h2>
&lt;p>PG 在 SQL feature 上長期領先 MySQL：&lt;/p>
&lt;ul>
&lt;li>2009 (PG 8.4)：CTE / window function / recursive query&lt;/li>
&lt;li>2013 (PG 9.3)：lateral derived table / materialized view&lt;/li>
&lt;li>2014 (PG 9.4)：JSONB / partial index 早就有 / GIN index&lt;/li>
&lt;li>2015 (PG 9.5)：UPSERT (&lt;code>ON CONFLICT&lt;/code>)&lt;/li>
&lt;li>2017 (PG 10)：declarative partitioning / logical replication / multi-column statistics&lt;/li>
&lt;/ul>
&lt;p>MySQL 8.0（2018）才補 CTE / window / lateral / JSON_TABLE / hash join — &lt;em>PG 早 9 年起步&lt;/em>。&lt;/p>
&lt;p>對 &lt;em>從 MySQL 評估 PG&lt;/em> 的讀者來說、PG 的 SQL 工程深度不只是「該有的都有」、更多是「PG 結構性領先的特性 + MySQL 8.0 補了哪些 + PG 仍領先哪些」。&lt;/p>
&lt;p>跟 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/vendors/mysql/modern-sql-features/" data-link-title="MySQL 8.0 Modern SQL：CTE / window function / JSON_TABLE 不是「終於跟上 PG」、是進入 SQL 工程深度的入場券" data-link-desc="MySQL 8.0 在 SQL 特性上 *終於補齊* CTE、window function、lateral derived table、JSON_TABLE、hash join 等現代 SQL 特性。本文走 5 個關鍵特性、各自實際 production 場景、跟 PostgreSQL 對應特性的行為差異（特別是 JSON_TABLE vs PG JSONB / jsonb_path_query）、配置 / migration 注意事項、5 production 踩雷（CTE 不 materialize / window function 大量 sort spill / JSON_TABLE 跟 generated column 取捨 / hash join 預設沒開 / recursive CTE 深度上限）">MySQL Modern SQL Features&lt;/a> 對比視角：&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>本文是 <a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL</a> overview 的 implementation-layer deep article。Overview 已說明 PG 在 OLTP 譜系的定位、本文聚焦 <em>SQL features baseline</em> — PG 早期就有的、MySQL 8.0 才補的、PG 仍領先的、給從 MySQL 評估 PG 的讀者 reference。</p></blockquote>
<hr>
<h2 id="pg-sql-工程深度的歷史錨點">PG SQL 工程深度的歷史錨點</h2>
<p>PG 在 SQL feature 上長期領先 MySQL：</p>
<ul>
<li>2009 (PG 8.4)：CTE / window function / recursive query</li>
<li>2013 (PG 9.3)：lateral derived table / materialized view</li>
<li>2014 (PG 9.4)：JSONB / partial index 早就有 / GIN index</li>
<li>2015 (PG 9.5)：UPSERT (<code>ON CONFLICT</code>)</li>
<li>2017 (PG 10)：declarative partitioning / logical replication / multi-column statistics</li>
</ul>
<p>MySQL 8.0（2018）才補 CTE / window / lateral / JSON_TABLE / hash join — <em>PG 早 9 年起步</em>。</p>
<p>對 <em>從 MySQL 評估 PG</em> 的讀者來說、PG 的 SQL 工程深度不只是「該有的都有」、更多是「PG 結構性領先的特性 + MySQL 8.0 補了哪些 + PG 仍領先哪些」。</p>
<p>跟 <a href="/blog/backend/01-database/vendors/mysql/modern-sql-features/" data-link-title="MySQL 8.0 Modern SQL：CTE / window function / JSON_TABLE 不是「終於跟上 PG」、是進入 SQL 工程深度的入場券" data-link-desc="MySQL 8.0 在 SQL 特性上 *終於補齊* CTE、window function、lateral derived table、JSON_TABLE、hash join 等現代 SQL 特性。本文走 5 個關鍵特性、各自實際 production 場景、跟 PostgreSQL 對應特性的行為差異（特別是 JSON_TABLE vs PG JSONB / jsonb_path_query）、配置 / migration 注意事項、5 production 踩雷（CTE 不 materialize / window function 大量 sort spill / JSON_TABLE 跟 generated column 取捨 / hash join 預設沒開 / recursive CTE 深度上限）">MySQL Modern SQL Features</a> 對比視角：</p>
<ul>
<li>MySQL 8.0 視角：「我終於補齊 + 跟 PG 對比」</li>
<li>PG 視角：「我長期領先 + MySQL 8.0 才追上某些、其他我仍領先」</li>
</ul>
<h2 id="pg-結構性領先特性mysql-沒對應--弱對應">PG 結構性領先特性（MySQL 沒對應 / 弱對應）</h2>
<h3 id="1-materialized-view">1. Materialized View</h3>
<p>PG 9.3+ 內建 materialized view：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="n">MATERIALIZED</span><span class="w"> </span><span class="k">VIEW</span><span class="w"> </span><span class="n">orders_summary</span><span class="w"> </span><span class="k">AS</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="n">user_id</span><span class="p">,</span><span class="w"> </span><span class="k">COUNT</span><span class="p">(</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">order_count</span><span class="p">,</span><span class="w"> </span><span class="k">SUM</span><span class="p">(</span><span class="n">amount</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">total</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"></span><span class="k">FROM</span><span class="w"> </span><span class="n">orders</span><span class="w"> </span><span class="k">GROUP</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">user_id</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w"></span><span class="c1">-- 手動 refresh
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"></span><span class="n">REFRESH</span><span class="w"> </span><span class="n">MATERIALIZED</span><span class="w"> </span><span class="k">VIEW</span><span class="w"> </span><span class="n">orders_summary</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w"></span><span class="c1">-- 或 concurrent refresh（PG 9.4+、不 lock read）
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="c1"></span><span class="n">REFRESH</span><span class="w"> </span><span class="n">MATERIALIZED</span><span class="w"> </span><span class="k">VIEW</span><span class="w"> </span><span class="n">CONCURRENTLY</span><span class="w"> </span><span class="n">orders_summary</span><span class="p">;</span></span></span></code></pre></div><p>用途：</p>
<ul>
<li>預計算複雜 aggregation、查詢時極快</li>
<li>Concurrent refresh 不 lock read</li>
<li>可建 index on materialized view</li>
</ul>
<p><strong>MySQL 對應</strong>：沒原生 materialized view。常見替代：</p>
<ul>
<li>Trigger + summary table（手動維護）</li>
<li>Application 層 caching layer</li>
<li>用 view + cache layer（不是 materialization）</li>
</ul>
<p>MySQL 8.0+ 仍無原生 materialized view。</p>
<h3 id="2-partial-index">2. Partial Index</h3>
<p>PG 預設支援 partial index — 對 <em>滿足條件的 row</em> 才建 index：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">-- 只對 active user 建 index
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_users_active_email</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">users</span><span class="p">(</span><span class="n">email</span><span class="p">)</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;active&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w"></span><span class="c1">-- Index size 比 full index 小很多、query 性能跟 full index 一樣
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">users</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;active&#39;</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="n">email</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;x@y.com&#39;</span><span class="p">;</span></span></span></code></pre></div><p>用途：</p>
<ul>
<li><em>Soft-delete</em> 場景：對 <code>deleted_at IS NULL</code> 建 partial index</li>
<li><em>Hot subset</em> 場景：對 <code>status = 'pending'</code> 等熱資料建 partial</li>
<li>Index 大小 / 寫入成本大降</li>
</ul>
<p><strong>MySQL 對應</strong>：MySQL 沒原生 partial index。MySQL 8.0+ 有 <em>functional index</em> 但跟 partial 不同。MySQL 替代：</p>
<ul>
<li>Generated column + index（接近、但維護複雜）</li>
<li>或接受 full index cost</li>
</ul>
<h3 id="3-foreign-data-wrapper-fdw">3. Foreign Data Wrapper (FDW)</h3>
<p>PG FDW 讓 query 跨外部資料源：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="n">EXTENSION</span><span class="w"> </span><span class="n">postgres_fdw</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="n">SERVER</span><span class="w"> </span><span class="n">remote_db</span><span class="w"> </span><span class="k">FOREIGN</span><span class="w"> </span><span class="k">DATA</span><span class="w"> </span><span class="n">WRAPPER</span><span class="w"> </span><span class="n">postgres_fdw</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="k">OPTIONS</span><span class="w"> </span><span class="p">(</span><span class="k">host</span><span class="w"> </span><span class="s1">&#39;remote.example.com&#39;</span><span class="p">,</span><span class="w"> </span><span class="n">dbname</span><span class="w"> </span><span class="s1">&#39;analytics&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">USER</span><span class="w"> </span><span class="n">MAPPING</span><span class="w"> </span><span class="k">FOR</span><span class="w"> </span><span class="n">localuser</span><span class="w"> </span><span class="n">SERVER</span><span class="w"> </span><span class="n">remote_db</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w"></span><span class="k">OPTIONS</span><span class="w"> </span><span class="p">(</span><span class="k">user</span><span class="w"> </span><span class="s1">&#39;remoteuser&#39;</span><span class="p">,</span><span class="w"> </span><span class="n">password</span><span class="w"> </span><span class="s1">&#39;...&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">FOREIGN</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">remote_orders</span><span class="w"> </span><span class="p">(</span><span class="n">id</span><span class="w"> </span><span class="nb">INT</span><span class="p">,</span><span class="w"> </span><span class="p">...)</span><span class="w"> </span><span class="n">SERVER</span><span class="w"> </span><span class="n">remote_db</span><span class="w"> </span><span class="k">OPTIONS</span><span class="w"> </span><span class="p">(</span><span class="k">table_name</span><span class="w"> </span><span class="s1">&#39;orders&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="c1">-- 在 local PG query remote table
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">remote_orders</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">100</span><span class="p">;</span></span></span></code></pre></div><p>支援 FDW：<code>postgres_fdw</code> / <code>mysql_fdw</code> / <code>oracle_fdw</code> / <code>mongo_fdw</code> / <code>file_fdw</code> / <code>redis_fdw</code> 等。</p>
<p><strong>MySQL 對應</strong>：MySQL 8.0+ 有 FEDERATED engine（受限、不推薦）。實務上 MySQL 跨 DB query 用 application 層處理。</p>
<h3 id="4-jsonb--gin-indexpg-結構性優勢">4. JSONB + GIN Index（PG 結構性優勢）</h3>
<p>PG JSONB 是 <em>binary 儲存</em> + 可 <em>直接 GIN index</em>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">products</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">SERIAL</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="n">metadata</span><span class="w"> </span><span class="n">JSONB</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="c1">-- GIN index over JSONB
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c1"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">INDEX</span><span class="w"> </span><span class="n">idx_products_metadata</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">products</span><span class="w"> </span><span class="k">USING</span><span class="w"> </span><span class="n">GIN</span><span class="w"> </span><span class="p">(</span><span class="n">metadata</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w"></span><span class="c1">-- 快 query
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="c1"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">products</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">metadata</span><span class="w"> </span><span class="o">@&gt;</span><span class="w"> </span><span class="s1">&#39;{&#34;category&#34;: &#34;shoes&#34;}&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">products</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">metadata</span><span class="w"> </span><span class="o">@?</span><span class="w"> </span><span class="s1">&#39;$.variants[*].price &gt; 100&#39;</span><span class="p">;</span></span></span></code></pre></div><p><strong>MySQL 對應</strong>：MySQL 8.0 JSON_TABLE 是 SQL standard、但 <em>index 必須 generated column workaround</em>（不能 GIN index over JSON）。</p>
<p>詳見 <a href="/blog/backend/01-database/vendors/mysql/modern-sql-features/" data-link-title="MySQL 8.0 Modern SQL：CTE / window function / JSON_TABLE 不是「終於跟上 PG」、是進入 SQL 工程深度的入場券" data-link-desc="MySQL 8.0 在 SQL 特性上 *終於補齊* CTE、window function、lateral derived table、JSON_TABLE、hash join 等現代 SQL 特性。本文走 5 個關鍵特性、各自實際 production 場景、跟 PostgreSQL 對應特性的行為差異（特別是 JSON_TABLE vs PG JSONB / jsonb_path_query）、配置 / migration 注意事項、5 production 踩雷（CTE 不 materialize / window function 大量 sort spill / JSON_TABLE 跟 generated column 取捨 / hash join 預設沒開 / recursive CTE 深度上限）">MySQL Modern SQL Features</a> JSON_TABLE vs PG JSONB 對比段。</p>
<h3 id="5-range-types--exclusion-constraints">5. Range Types + Exclusion Constraints</h3>
<p>PG range types + exclusion constraints 防止 <em>時間範圍重疊</em>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">reservations</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">SERIAL</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="n">room_id</span><span class="w"> </span><span class="nb">INT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="n">during</span><span class="w"> </span><span class="n">TSRANGE</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="n">EXCLUDE</span><span class="w"> </span><span class="k">USING</span><span class="w"> </span><span class="n">GIST</span><span class="w"> </span><span class="p">(</span><span class="n">room_id</span><span class="w"> </span><span class="k">WITH</span><span class="w"> </span><span class="o">=</span><span class="p">,</span><span class="w"> </span><span class="n">during</span><span class="w"> </span><span class="k">WITH</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w"></span><span class="c1">-- INSERT 重疊 booking 自動 reject
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">reservations</span><span class="w"> </span><span class="p">(</span><span class="n">room_id</span><span class="p">,</span><span class="w"> </span><span class="n">during</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w"></span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;[2026-05-19 10:00, 2026-05-19 12:00)&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">reservations</span><span class="w"> </span><span class="p">(</span><span class="n">room_id</span><span class="p">,</span><span class="w"> </span><span class="n">during</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"></span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;[2026-05-19 11:00, 2026-05-19 13:00)&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w"></span><span class="c1">-- ERROR: conflicting key value violates exclusion constraint</span></span></span></code></pre></div><p><strong>MySQL 對應</strong>：完全沒對應、必須 application 層 enforce。</p>
<h3 id="6-check-constraint--domain-type">6. CHECK Constraint + Domain Type</h3>
<p>PG <code>CHECK</code> constraint 真執行（MySQL 8.0 才補）+ user-defined <code>DOMAIN</code>：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">DOMAIN</span><span class="w"> </span><span class="n">positive_int</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="nb">INT</span><span class="w"> </span><span class="k">CHECK</span><span class="w"> </span><span class="p">(</span><span class="n">VALUE</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">orders</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">SERIAL</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">    </span><span class="n">quantity</span><span class="w"> </span><span class="n">positive_int</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="n">amount</span><span class="w"> </span><span class="nb">DECIMAL</span><span class="w"> </span><span class="k">CHECK</span><span class="w"> </span><span class="p">(</span><span class="n">amount</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w"></span><span class="p">);</span></span></span></code></pre></div><p><strong>MySQL 對應</strong>：8.0+ 有 CHECK constraint enforcement（5.7 可寫但不執行）。沒 user-defined DOMAIN。</p>
<h3 id="7-extension-ecosystem">7. Extension Ecosystem</h3>
<p>PG extension 是 <em>結構優勢</em>：</p>
<ul>
<li><code>pg_partman</code>：自動 partition lifecycle</li>
<li><code>pg_repack</code>：online table rewrite</li>
<li><code>pg_stat_statements</code>：query stats</li>
<li><code>pgvector</code>：vector similarity search</li>
<li><code>pg_cron</code>：scheduled job</li>
<li><code>PostGIS</code>：GIS</li>
<li><code>TimescaleDB</code>：time-series</li>
<li><code>Citus</code>：sharding</li>
</ul>
<p><strong>MySQL 對應</strong>：MySQL plugin 機制有、生態遠遠不如。詳見 <em>PG Extension Ecosystem</em> 篇（待寫）。</p>
<h2 id="mysql-80-補齊的-pg-既有特性">MySQL 8.0 補齊的 PG 既有特性</h2>
<table>
  <thead>
      <tr>
          <th>特性</th>
          <th>PG 推出</th>
          <th>MySQL 推出</th>
          <th>差異後說明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CTE</td>
          <td>8.4 (2009)</td>
          <td>8.0 (2018)</td>
          <td>MySQL 補語法、行為 PG 12+ 跟 MySQL 接近</td>
      </tr>
      <tr>
          <td>Window function</td>
          <td>8.4 (2009)</td>
          <td>8.0 (2018)</td>
          <td>兩家都標準、frame spec 細節有差</td>
      </tr>
      <tr>
          <td>Lateral derived table</td>
          <td>9.3 (2013)</td>
          <td>8.0.14 (2019)</td>
          <td>MySQL 後加、planner 不如 PG 成熟</td>
      </tr>
      <tr>
          <td>Hash join</td>
          <td>早就有</td>
          <td>8.0.18 (2019)</td>
          <td>MySQL 受限（equality on indexed column）</td>
      </tr>
      <tr>
          <td>JSON_TABLE</td>
          <td>17 (2024)</td>
          <td>8.0 (2018)</td>
          <td>MySQL 較早、PG 17+ 補進、PG 自己有 JSONB 路線</td>
      </tr>
      <tr>
          <td>CHECK constraint</td>
          <td>早就有</td>
          <td>8.0 (2018)</td>
          <td>MySQL 5.7 可寫但不執行</td>
      </tr>
      <tr>
          <td>Role-based auth</td>
          <td>早就有</td>
          <td>8.0 (2018)</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Atomic DDL</td>
          <td>早就有</td>
          <td>8.0 (2018)</td>
          <td>-</td>
      </tr>
      <tr>
          <td>Common keyword</td>
          <td>完整</td>
          <td>8.0 補</td>
          <td>MySQL 5.7 缺很多 (window/rank/lateral 等)</td>
      </tr>
  </tbody>
</table>
<p>MySQL 8.0 是 <em>補齊 9 年 SQL standard 落後</em>、不是 <em>新領先 PG</em>。</p>
<h2 id="pg-仍領先的特性">PG 仍領先的特性</h2>
<p>對應「MySQL 8.0 補了 → PG 仍沒輸」的視角。以下 14 條中、<em>production 影響最大</em> 的是 Materialized view / Partial index / JSONB GIN / Full-text search 跟 Range / Exclusion constraints（schema-level expressiveness）；<em>次要但常用</em> 的是 Multi-column statistics 跟 Procedural language；<em>非典型但 niche 重要</em> 的是 User-defined DOMAIN / Generic table inheritance（讀者不必然知道、但 ORM 跟 schema migration 工具會用）：</p>
<table>
  <thead>
      <tr>
          <th>PG 領先特性</th>
          <th>MySQL 對應狀態</th>
          <th>補充</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Materialized view</td>
          <td>無原生</td>
          <td>application-side 重算成本高</td>
      </tr>
      <tr>
          <td>Partial index</td>
          <td>無（functional index 不等同）</td>
          <td>對 boolean / status column 救 storage</td>
      </tr>
      <tr>
          <td>FDW</td>
          <td>弱（FEDERATED engine 不推薦）</td>
          <td>跨 DB query escape hatch</td>
      </tr>
      <tr>
          <td>JSONB GIN index</td>
          <td>無（generated column workaround）</td>
          <td>JSON workload 結構性差</td>
      </tr>
      <tr>
          <td>Range types</td>
          <td>無</td>
          <td>booking / availability schema 救命</td>
      </tr>
      <tr>
          <td>Exclusion constraints</td>
          <td>無</td>
          <td>range overlap 防護</td>
      </tr>
      <tr>
          <td>User-defined DOMAIN</td>
          <td>無</td>
          <td>column-level type constraint</td>
      </tr>
      <tr>
          <td>Extension ecosystem</td>
          <td>弱</td>
          <td>pgvector / TimescaleDB / PostGIS</td>
      </tr>
      <tr>
          <td>Full-text search 成熟</td>
          <td>InnoDB FTS 較弱</td>
          <td>tsvector + GIN + pg_trgm 三層</td>
      </tr>
      <tr>
          <td>Multi-column statistics</td>
          <td>8.0 histograms 部分對應、PG 更廣</td>
          <td>planner 更準</td>
      </tr>
      <tr>
          <td>Procedural language</td>
          <td>PL/pgSQL + 多語言（PL/Python / PL/Perl 等）</td>
          <td>Stored procedure（不擴語言）</td>
      </tr>
      <tr>
          <td>Recursive CTE 深度</td>
          <td>Unlimited</td>
          <td>1000（cte_max_recursion_depth）</td>
      </tr>
      <tr>
          <td>LSN-based replication</td>
          <td>簡潔</td>
          <td>binlog file+position（GTID 緩解）</td>
      </tr>
      <tr>
          <td>Generic table inheritance</td>
          <td>早就有</td>
          <td>無（multi-tenant schema 結構用）</td>
      </tr>
  </tbody>
</table>
<h2 id="對從-mysql-評估-pg的讀者">對「從 MySQL 評估 PG」的讀者</h2>
<p>讀者通常從 MySQL 8.0 過來、問題是 <em>「PG 比 MySQL 強在哪、弱在哪」</em>：</p>
<h3 id="pg-比-mysql-強">PG 比 MySQL 強</h3>
<ul>
<li><em>SQL 工程深度</em>：上面列的 7 個結構優勢</li>
<li><em>Extension ecosystem</em>：pgvector / TimescaleDB / Citus / pg_partman 等</li>
<li><em>Optimizer</em>：planner 對複雜 query 更成熟</li>
<li><em>Concurrency model</em>：MVCC + 少 lock（<a href="/blog/backend/01-database/vendors/postgresql/mvcc-lock-model/" data-link-title="PostgreSQL MVCC &#43; Lock Model：為什麼 PG 比 MySQL 少 deadlock、但 vacuum 是別的代價" data-link-desc="PG 用 *MVCC-heavy &#43; 少 explicit lock* 的並行控制、跟 MySQL InnoDB 的 *lock-based*（record / gap / next-key）相反。本文走 MVCC 機制（tuple version &#43; xmin/xmax &#43; visibility）、PG 4 種 lock（row-level / table-level / advisory / predicate）、預測 SERIALIZABLE 行為、5 production 踩雷（idle transaction 卡 vacuum / SELECT FOR UPDATE 跨 transaction / advisory lock 沒釋放 / bloat 不是 vacuum 問題 / predicate lock 在 SSI 下 rollback）、跟 MySQL lock-contention sibling 對比">MVCC + Lock Model</a>）</li>
</ul>
<h3 id="pg-比-mysql-弱">PG 比 MySQL 弱</h3>
<ul>
<li><em>Replication 機制簡潔度</em>：MySQL GTID 比 PG WAL + replication slot 配置簡單（<a href="/blog/backend/01-database/vendors/postgresql/replication-topology/" data-link-title="PostgreSQL Replication Topology：async / sync / quorum 三模式跟 LSN &#43; replication slot 的三軸組合" data-link-desc="PostgreSQL streaming replication 不是「sync 或 async」、是 *durability / latency / consistency* 三軸組合 &#43; LSN-based 進度追蹤 &#43; replication slot 治理。本文走 3 軸取捨模型、async / sync / quorum-based sync 行為對比、LSN &#43; replication slot 機制、配置 step-by-step、5 production 踩雷（standby lag 暴衝 / sync standby 退回 async / orphan replication slot / cascading replication 雪崩 / failover 後 timeline 分歧）、跟 Patroni HA &#43; logical replication 整合">Replication Topology</a>）</li>
<li><em>Sharding ecosystem</em>：Vitess / PlanetScale 比 Citus 規模驗證高</li>
<li><em>Operational tooling 廣度</em>：pt-toolkit / gh-ost / Orchestrator 等</li>
<li><em>VACUUM 維護</em>：PG MVCC 必須 VACUUM、autovacuum 配錯議題多（<a href="/blog/backend/01-database/vendors/postgresql/autovacuum-tuning/" data-link-title="PostgreSQL autovacuum tuning：為什麼你的 autovacuum 永遠追不上 bloat" data-link-desc="MVCC 怎麼產生 dead tuple、autovacuum cost-based throttle 為什麼預設保守、per-table tuning 怎麼設、5 個 production 踩雷（cost_limit 太低 / 長 transaction blocks vacuum / anti-wraparound 在 peak / partition vacuum 滿 worker / index bloat 沒處理）、跟 partitioning &#43; monitoring 整合">Autovacuum Tuning</a>）</li>
</ul>
<h3 id="選-pg-的核心-driver">選 PG 的核心 driver</h3>
<p>對 SQL 工程深度、extension、複雜 query / OLAP-style workload 的場景、PG 仍是首選。對純簡單 OLTP + 大規模 sharding、MySQL + Vitess 仍 competitive。</p>
<h2 id="跟其他模組整合">跟其他模組整合</h2>
<ul>
<li><a href="/blog/backend/01-database/vendors/postgresql/mvcc-lock-model/" data-link-title="PostgreSQL MVCC &#43; Lock Model：為什麼 PG 比 MySQL 少 deadlock、但 vacuum 是別的代價" data-link-desc="PG 用 *MVCC-heavy &#43; 少 explicit lock* 的並行控制、跟 MySQL InnoDB 的 *lock-based*（record / gap / next-key）相反。本文走 MVCC 機制（tuple version &#43; xmin/xmax &#43; visibility）、PG 4 種 lock（row-level / table-level / advisory / predicate）、預測 SERIALIZABLE 行為、5 production 踩雷（idle transaction 卡 vacuum / SELECT FOR UPDATE 跨 transaction / advisory lock 沒釋放 / bloat 不是 vacuum 問題 / predicate lock 在 SSI 下 rollback）、跟 MySQL lock-contention sibling 對比">MVCC + Lock Model</a>：PG MVCC 是 SQL feature 的並行控制基礎</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/query-optimization/" data-link-title="PostgreSQL Query Optimization：EXPLAIN ANALYZE / pg_hint_plan / auto_explain 三層工具跟 4 個 case" data-link-desc="PG query 慢的根因常是 *planner 選錯 plan 或 statistics 過時*。本文從 4 個 production case 開場（seq scan vs index / hash vs nested loop / 多 column 統計缺 / parallel query 沒觸發）、走 EXPLAIN / EXPLAIN ANALYZE / auto_explain 三層工具、pg_hint_plan extension 跟 planner GUC 取捨、5 production 踩雷（ANALYZE 過時 / multi-column statistics / cost-base setting 不對齊硬體 / random_page_cost SSD 沒調 / parallel query 配置）、跟 MySQL query-optimization sibling 對比">Query Optimization</a>：PG planner 對 window / CTE / hash join 成熟</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/citus-distributed/" data-link-title="PostgreSQL Citus Distributed：用 extension 把 PG 變成 sharded cluster" data-link-desc="Citus 是 PG extension、把單機 PG 變成 *coordinator &#43; worker* sharded cluster、保留 PG SQL &#43; 加 distributed table &#43; reference table &#43; columnar storage。本文走 Citus 架構（coordinator / worker / distribution column）、3 種 table type（distributed / reference / local）、配置 step-by-step、5 production 踩雷（distribution column 選錯 / cross-shard transaction / reference table 過大 / colocate 不對齊 / worker failover）、跟 MySQL Vitess sharding sibling 對比">Citus Distributed</a>：extension 之一、體現 extension 生態</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/autovacuum-tuning/" data-link-title="PostgreSQL autovacuum tuning：為什麼你的 autovacuum 永遠追不上 bloat" data-link-desc="MVCC 怎麼產生 dead tuple、autovacuum cost-based throttle 為什麼預設保守、per-table tuning 怎麼設、5 個 production 踩雷（cost_limit 太低 / 長 transaction blocks vacuum / anti-wraparound 在 peak / partition vacuum 滿 worker / index bloat 沒處理）、跟 partitioning &#43; monitoring 整合">Autovacuum Tuning</a>：MVCC 代價、跟 SQL feature 並行控制相關</li>
</ul>
<h2 id="相關連結">相關連結</h2>
<ul>
<li><a href="/blog/backend/01-database/vendors/postgresql/" data-link-title="PostgreSQL" data-link-desc="多用途 OLTP 主流關聯式資料庫、MVCC、豐富 SQL 特性、是 Aurora / Cosmos DB / Spanner / CockroachDB / Aurora DSQL 的相容目標">PostgreSQL vendor overview</a></li>
<li><a href="/blog/backend/01-database/vendors/postgresql/mvcc-lock-model/" data-link-title="PostgreSQL MVCC &#43; Lock Model：為什麼 PG 比 MySQL 少 deadlock、但 vacuum 是別的代價" data-link-desc="PG 用 *MVCC-heavy &#43; 少 explicit lock* 的並行控制、跟 MySQL InnoDB 的 *lock-based*（record / gap / next-key）相反。本文走 MVCC 機制（tuple version &#43; xmin/xmax &#43; visibility）、PG 4 種 lock（row-level / table-level / advisory / predicate）、預測 SERIALIZABLE 行為、5 production 踩雷（idle transaction 卡 vacuum / SELECT FOR UPDATE 跨 transaction / advisory lock 沒釋放 / bloat 不是 vacuum 問題 / predicate lock 在 SSI 下 rollback）、跟 MySQL lock-contention sibling 對比">PG MVCC + Lock Model</a>（concurrency 基礎）</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/query-optimization/" data-link-title="PostgreSQL Query Optimization：EXPLAIN ANALYZE / pg_hint_plan / auto_explain 三層工具跟 4 個 case" data-link-desc="PG query 慢的根因常是 *planner 選錯 plan 或 statistics 過時*。本文從 4 個 production case 開場（seq scan vs index / hash vs nested loop / 多 column 統計缺 / parallel query 沒觸發）、走 EXPLAIN / EXPLAIN ANALYZE / auto_explain 三層工具、pg_hint_plan extension 跟 planner GUC 取捨、5 production 踩雷（ANALYZE 過時 / multi-column statistics / cost-base setting 不對齊硬體 / random_page_cost SSD 沒調 / parallel query 配置）、跟 MySQL query-optimization sibling 對比">PG Query Optimization</a>（planner 成熟度）</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/citus-distributed/" data-link-title="PostgreSQL Citus Distributed：用 extension 把 PG 變成 sharded cluster" data-link-desc="Citus 是 PG extension、把單機 PG 變成 *coordinator &#43; worker* sharded cluster、保留 PG SQL &#43; 加 distributed table &#43; reference table &#43; columnar storage。本文走 Citus 架構（coordinator / worker / distribution column）、3 種 table type（distributed / reference / local）、配置 step-by-step、5 production 踩雷（distribution column 選錯 / cross-shard transaction / reference table 過大 / colocate 不對齊 / worker failover）、跟 MySQL Vitess sharding sibling 對比">PG Citus Distributed</a>（extension example）</li>
<li><a href="/blog/backend/01-database/vendors/postgresql/autovacuum-tuning/" data-link-title="PostgreSQL autovacuum tuning：為什麼你的 autovacuum 永遠追不上 bloat" data-link-desc="MVCC 怎麼產生 dead tuple、autovacuum cost-based throttle 為什麼預設保守、per-table tuning 怎麼設、5 個 production 踩雷（cost_limit 太低 / 長 transaction blocks vacuum / anti-wraparound 在 peak / partition vacuum 滿 worker / index bloat 沒處理）、跟 partitioning &#43; monitoring 整合">PG Autovacuum Tuning</a>（MVCC 維護）</li>
<li><a href="/blog/backend/01-database/vendors/mysql/modern-sql-features/" data-link-title="MySQL 8.0 Modern SQL：CTE / window function / JSON_TABLE 不是「終於跟上 PG」、是進入 SQL 工程深度的入場券" data-link-desc="MySQL 8.0 在 SQL 特性上 *終於補齊* CTE、window function、lateral derived table、JSON_TABLE、hash join 等現代 SQL 特性。本文走 5 個關鍵特性、各自實際 production 場景、跟 PostgreSQL 對應特性的行為差異（特別是 JSON_TABLE vs PG JSONB / jsonb_path_query）、配置 / migration 注意事項、5 production 踩雷（CTE 不 materialize / window function 大量 sort spill / JSON_TABLE 跟 generated column 取捨 / hash join 預設沒開 / recursive CTE 深度上限）">MySQL Modern SQL Features</a>（sibling、反向視角）</li>
<li>官方：<a href="https://www.postgresql.org/about/featurematrix/">PostgreSQL Features</a></li>
</ul>
]]></content:encoded></item></channel></rss>