<?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>Analyze on Tarragon</title><link>https://tarrragon.github.io/blog/tags/analyze/</link><description>Recent content in Analyze on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 01 Sep 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/analyze/index.xml" rel="self" type="application/rss+xml"/><item><title>Query Statistics（統計資訊）</title><link>https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/query-statistics/</link><pubDate>Tue, 01 Sep 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/query-statistics/</guid><description>&lt;p>統計資訊是引擎另外去數出來、存在一旁的一份摘要：每張表大概多少列、某個欄位的值大概怎麼分布。它不在查詢的文字裡，也不在資料表裡——資料本身有多少列是一回事，引擎「知道」有多少列是另一回事，而 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/query-optimizer/" data-link-title="Query Optimizer（查詢最佳化器）" data-link-desc="想知道是誰決定了執行順序、或為什麼同一段查詢換個時間變慢時">query optimizer&lt;/a> 選 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/query-plan/" data-link-title="Query Plan（執行計畫）" data-link-desc="想知道一段查詢實際會怎麼被執行、或被要求「先看計畫」時">query plan&lt;/a> 時讀的是後者。&lt;/p>
&lt;h2 id="誰產生它什麼時候">誰產生它，什麼時候&lt;/h2>
&lt;p>多數引擎用一道明確的指令收集：SQLite 與 PostgreSQL 都是 &lt;code>ANALYZE&lt;/code>，PostgreSQL 另有 autovacuum 在背景定期重收。剛建好的資料庫沒有統計，剛大量寫入之後統計是舊的。&lt;/p>
&lt;p>沒有統計的時候引擎不會拒絕執行，它退回去用別的線索。SQLite 在這種情形下照查詢文字裡表出現的先後決定計畫——&lt;strong>書寫順序在這裡才有影響力，而那是引擎沒有別的資訊時的退路，不是它的依據&lt;/strong>。跑過 &lt;code>ANALYZE&lt;/code> 之後同樣兩種寫法得到同一個計畫。&lt;/p>
&lt;h2 id="它決定的是估計不是事實">它決定的是估計，不是事實&lt;/h2>
&lt;p>最佳化器比較的是估計代價，而估計來自這份摘要。所以判斷品質取決於統計有多新，與查詢寫得多好無關：&lt;/p>
&lt;ul>
&lt;li>查詢沒動而某天變慢，變的通常是統計而不是寫法——歸因的起點放在統計的新舊&lt;/li>
&lt;li>計畫上的估計列數與實際列數差很多，先懷疑統計，再懷疑寫法&lt;/li>
&lt;li>在小資料上量到的計畫套到 production 常常對不上，因為兩邊的統計不同&lt;/li>
&lt;/ul>
&lt;h2 id="它與索引是兩件事">它與索引是兩件事&lt;/h2>
&lt;p>&lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/indexing/" data-link-title="Index（索引）" data-link-desc="看到執行計畫寫著走索引或建索引、想知道那是什麼時">索引&lt;/a>決定引擎有沒有一條快的存取路徑可以走，統計決定它知不知道該走那一條。索引建了而統計沒更新的時候，最佳化器可能估錯而不走它——兩者要一起看。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>統計資訊是「查詢的文字不足以決定執行方式」這件事的具體內容。查詢說要什麼結果，&lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/query-optimizer/" data-link-title="Query Optimizer（查詢最佳化器）" data-link-desc="想知道是誰決定了執行順序、或為什麼同一段查詢換個時間變慢時">query optimizer&lt;/a> 決定怎麼拿，而它決定的依據既不在查詢裡也不在資料裡——它在這一份另外存著的摘要上。&lt;/p>
&lt;p>這讓同一段 SQL 沒有固定的執行方式：資料沒變、索引沒變、查詢沒變，只要統計重收過一次，&lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/knowledge-cards/query-plan/" data-link-title="Query Plan（執行計畫）" data-link-desc="想知道一段查詢實際會怎麼被執行、或被要求「先看計畫」時">query plan&lt;/a> 就可能不同。所以「這段查詢的計畫是什麼」這個問題要連同資料庫的狀態一起問。&lt;/p>
&lt;h2 id="往下走">往下走&lt;/h2>
&lt;p>有無統計的兩個計畫並排、以及書寫順序為什麼只是退路，在 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/declarative-not-procedural/" data-link-title="SQL.1 宣告式的紅利與代價：書寫、求值與執行順序各自由誰決定" data-link-desc="SQL 寫結果而不寫算法帶來的後果：同一個問題換書寫順序之後在有無統計資訊時拿到的計畫、求值順序擋下的寫法，以及代價為什麼要向引擎問">SQL.1 宣告式的紅利與代價&lt;/a>。統計、索引與資料分布三者各自決定代價的哪一部分，在 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/sql/cost-lives-in-the-plan/" data-link-title="SQL.17 查詢的代價：資料分布、索引與寫法各自的影響" data-link-desc="找重複值的分組、自連接與 EXISTS 在有無索引與不同重複次數下的實測時間與計畫，以及比較查詢快慢時要補上的資料量、分布、索引與統計資訊">SQL.17 查詢的代價&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>統計資訊是引擎另外去數出來、存在一旁的一份摘要：每張表大概多少列、某個欄位的值大概怎麼分布。它不在查詢的文字裡，也不在資料表裡——資料本身有多少列是一回事，引擎「知道」有多少列是另一回事，而 <a href="/blog/backend/01-database/sql/knowledge-cards/query-optimizer/" data-link-title="Query Optimizer（查詢最佳化器）" data-link-desc="想知道是誰決定了執行順序、或為什麼同一段查詢換個時間變慢時">query optimizer</a> 選 <a href="/blog/backend/01-database/sql/knowledge-cards/query-plan/" data-link-title="Query Plan（執行計畫）" data-link-desc="想知道一段查詢實際會怎麼被執行、或被要求「先看計畫」時">query plan</a> 時讀的是後者。</p>
<h2 id="誰產生它什麼時候">誰產生它，什麼時候</h2>
<p>多數引擎用一道明確的指令收集：SQLite 與 PostgreSQL 都是 <code>ANALYZE</code>，PostgreSQL 另有 autovacuum 在背景定期重收。剛建好的資料庫沒有統計，剛大量寫入之後統計是舊的。</p>
<p>沒有統計的時候引擎不會拒絕執行，它退回去用別的線索。SQLite 在這種情形下照查詢文字裡表出現的先後決定計畫——<strong>書寫順序在這裡才有影響力，而那是引擎沒有別的資訊時的退路，不是它的依據</strong>。跑過 <code>ANALYZE</code> 之後同樣兩種寫法得到同一個計畫。</p>
<h2 id="它決定的是估計不是事實">它決定的是估計，不是事實</h2>
<p>最佳化器比較的是估計代價，而估計來自這份摘要。所以判斷品質取決於統計有多新，與查詢寫得多好無關：</p>
<ul>
<li>查詢沒動而某天變慢，變的通常是統計而不是寫法——歸因的起點放在統計的新舊</li>
<li>計畫上的估計列數與實際列數差很多，先懷疑統計，再懷疑寫法</li>
<li>在小資料上量到的計畫套到 production 常常對不上，因為兩邊的統計不同</li>
</ul>
<h2 id="它與索引是兩件事">它與索引是兩件事</h2>
<p><a href="/blog/backend/01-database/sql/knowledge-cards/indexing/" data-link-title="Index（索引）" data-link-desc="看到執行計畫寫著走索引或建索引、想知道那是什麼時">索引</a>決定引擎有沒有一條快的存取路徑可以走，統計決定它知不知道該走那一條。索引建了而統計沒更新的時候，最佳化器可能估錯而不走它——兩者要一起看。</p>
<h2 id="概念位置">概念位置</h2>
<p>統計資訊是「查詢的文字不足以決定執行方式」這件事的具體內容。查詢說要什麼結果，<a href="/blog/backend/01-database/sql/knowledge-cards/query-optimizer/" data-link-title="Query Optimizer（查詢最佳化器）" data-link-desc="想知道是誰決定了執行順序、或為什麼同一段查詢換個時間變慢時">query optimizer</a> 決定怎麼拿，而它決定的依據既不在查詢裡也不在資料裡——它在這一份另外存著的摘要上。</p>
<p>這讓同一段 SQL 沒有固定的執行方式：資料沒變、索引沒變、查詢沒變，只要統計重收過一次，<a href="/blog/backend/01-database/sql/knowledge-cards/query-plan/" data-link-title="Query Plan（執行計畫）" data-link-desc="想知道一段查詢實際會怎麼被執行、或被要求「先看計畫」時">query plan</a> 就可能不同。所以「這段查詢的計畫是什麼」這個問題要連同資料庫的狀態一起問。</p>
<h2 id="往下走">往下走</h2>
<p>有無統計的兩個計畫並排、以及書寫順序為什麼只是退路，在 <a href="/blog/backend/01-database/sql/declarative-not-procedural/" data-link-title="SQL.1 宣告式的紅利與代價：書寫、求值與執行順序各自由誰決定" data-link-desc="SQL 寫結果而不寫算法帶來的後果：同一個問題換書寫順序之後在有無統計資訊時拿到的計畫、求值順序擋下的寫法，以及代價為什麼要向引擎問">SQL.1 宣告式的紅利與代價</a>。統計、索引與資料分布三者各自決定代價的哪一部分，在 <a href="/blog/backend/01-database/sql/cost-lives-in-the-plan/" data-link-title="SQL.17 查詢的代價：資料分布、索引與寫法各自的影響" data-link-desc="找重複值的分組、自連接與 EXISTS 在有無索引與不同重複次數下的實測時間與計畫，以及比較查詢快慢時要補上的資料量、分布、索引與統計資訊">SQL.17 查詢的代價</a>。</p>
]]></content:encoded></item></channel></rss>