<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Performance Optimization on DuckDB Lab — 独立 DuckDB 中文技术社区与实验博客</title><link>https://duckdblab.org/zh/categories/performance-optimization/</link><description>Recent content in Performance Optimization on DuckDB Lab — 独立 DuckDB 中文技术社区与实验博客</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Sun, 04 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://duckdblab.org/zh/categories/performance-optimization/index.xml" rel="self" type="application/rss+xml"/><item><title>DuckDB 表采样深度解析：USING SAMPLE 与 TABLESAMPLE（以及采样为什么会更慢）</title><link>https://duckdblab.org/zh/post/duckdb-table-sampling-guide/</link><pubDate>Sun, 04 Oct 2026 00:00:00 +0000</pubDate><guid>https://duckdblab.org/zh/post/duckdb-table-sampling-guide/</guid><description>&lt;img src="https://duckdblab.org/images/posts/duckdb-table-sampling-guide/architecture.png" alt="Featured image of post DuckDB 表采样深度解析：USING SAMPLE 与 TABLESAMPLE（以及采样为什么会更慢）" /&gt;&lt;h2 id="引言直接采样就行了只对了一半"&gt;引言：&amp;ldquo;直接采样就行了&amp;quot;只对了一半
&lt;/h2&gt;&lt;p&gt;&amp;ldquo;直接采样就行了&amp;rdquo;——这是面对大表时最常见的建议。听起来很划算：只读 10% 的数据，就该得到快 10 倍的结果。但在 DuckDB 里，这个直觉&lt;strong&gt;只对了一半&lt;/strong&gt;，而错的那一半正是最容易踩坑的地方。&lt;/p&gt;
&lt;p&gt;DuckDB 支持两种采样语法：原生的 &lt;code&gt;USING SAMPLE&lt;/code&gt; 和 SQL 标准的 &lt;code&gt;TABLESAMPLE&lt;/code&gt;；背后则有三种算法：&lt;code&gt;SYSTEM&lt;/code&gt;、&lt;code&gt;BERNOULLI&lt;/code&gt;、&lt;code&gt;RESERVOIR&lt;/code&gt;。它们的行为差别很大，默认值不同，而且其中一种无论你要百分之几，都会&lt;strong&gt;悄悄扫完整张表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;下面所有数字都来自 DuckDB 1.5.4，那些反直觉的结论（包括一次采样让查询&lt;strong&gt;慢 16 倍&lt;/strong&gt;）都可以复现。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="两种语法一个容易混淆的重叠"&gt;两种语法，一个容易混淆的重叠
&lt;/h2&gt;&lt;p&gt;DuckDB 同时接受这两种写法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- DuckDB 原生
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- SQL 标准
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLESAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PERCENT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;看起来可以互换，其实不能。&lt;code&gt;USING SAMPLE&lt;/code&gt; 既接受百分比，也接受&lt;strong&gt;行数&lt;/strong&gt;；而 &lt;code&gt;TABLESAMPLE&lt;/code&gt; 把裸数字当百分比，把括号里的数字当成完全不同的东西 —— 这就是第一个陷阱的来源。&lt;/p&gt;
&lt;p&gt;先建一张表：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌──────────────┐
│ count_star() │
│ int64 │
├──────────────┤
│ 100000 │
└──────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="三种采样方法与它们的隐藏默认值"&gt;三种采样方法，与它们的隐藏默认值
&lt;/h2&gt;&lt;p&gt;&lt;img alt="DuckDB 采样架构：SYSTEM 下推进扫描，BERNOULLI 在扫描之上加算子" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://duckdblab.org/images/posts/duckdb-table-sampling-guide/architecture.png"&gt;&lt;/p&gt;
&lt;p&gt;两种语法背后是三种算法。选哪一种，同时决定了&lt;em&gt;结果&lt;/em&gt;和&lt;em&gt;代价&lt;/em&gt;：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;方法&lt;/th&gt;
 &lt;th&gt;机制&lt;/th&gt;
 &lt;th&gt;粒度&lt;/th&gt;
 &lt;th&gt;是否读全表&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;RESERVOIR&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;边扫描边维护蓄水池，输出精确大小的均匀样本&lt;/td&gt;
 &lt;td&gt;行&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;BERNOULLI&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;每行独立抛硬币&lt;/td&gt;
 &lt;td&gt;行&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;SYSTEM&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;随机挑选整块 / row group&lt;/td&gt;
 &lt;td&gt;块&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;否&lt;/strong&gt;（可下推）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;两种写法的默认方法并不相同&lt;/strong&gt;，这一点很容易被忽略：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 百分比形式：默认是 SYSTEM
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;system&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 行数形式：默认是 RESERVOIR
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在 10 万行数据上实测，三种方法对同一个&amp;quot;10%&amp;ldquo;给出的样本量明显不同：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;USING SAMPLE 10% (system) -&amp;gt; 8192 行
USING SAMPLE 10% (bernoulli) -&amp;gt; 10017 行
USING SAMPLE 10% (reservoir) -&amp;gt; 10000 行
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;RESERVOIR&lt;/code&gt; 精确；&lt;code&gt;BERNOULLI&lt;/code&gt; 统计上准确但有噪声；&lt;code&gt;SYSTEM&lt;/code&gt; 按块量化，可能偏离标称比例几个百分点 —— 这不是 bug，而是&amp;quot;能跳过 I/O&amp;quot;必须付出的代价。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五个语法陷阱与真实报错"&gt;五个语法陷阱与真实报错
&lt;/h2&gt;&lt;h3 id="陷阱一using-sample-必须是-from-里最后一项"&gt;陷阱一：USING SAMPLE 必须是 FROM 里最后一项
&lt;/h3&gt;&lt;p&gt;不能直接接 &lt;code&gt;WHERE&lt;/code&gt; 或 &lt;code&gt;GROUP BY&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;Parser Error: syntax error at or near &amp;#34;GROUP&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;把采样包进子查询即可：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bernoulli&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="陷阱二tablesample-system-n-非法"&gt;陷阱二：TABLESAMPLE SYSTEM (n) 非法
&lt;/h3&gt;&lt;p&gt;括号形式要求的是&lt;strong&gt;百分比&lt;/strong&gt;，小整数会被当成行数并报错：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLESAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;Error: Sample method System cannot be used with a discrete sample count,
either switch to reservoir sampling or use a percentage.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;应写成 &lt;code&gt;TABLESAMPLE SYSTEM (5%)&lt;/code&gt; 或裸写的 &lt;code&gt;TABLESAMPLE 5 PERCENT&lt;/code&gt;。&lt;code&gt;BERNOULLI&lt;/code&gt; 同理。&lt;/p&gt;
&lt;h3 id="陷阱三tablesample-reservoir-5-是-5-行"&gt;陷阱三：TABLESAMPLE RESERVOIR (5) 是 5 行
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TABLESAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;RESERVOIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌──────────────┐
│ 5 │
└──────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;是 5 行，不是 5%。&lt;code&gt;RESERVOIR&lt;/code&gt; 是唯一在括号形式里接受离散行数的算法。&lt;/p&gt;
&lt;h3 id="陷阱四system-可能返回零行"&gt;陷阱四：SYSTEM 可能返回零行
&lt;/h3&gt;&lt;p&gt;因为 &lt;code&gt;SYSTEM&lt;/code&gt; 按整块采样，如果整张表只有一个 row group，就没有块可挑。你要 5%，结果可能&lt;strong&gt;一行都没有&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;small&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;system&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌──────────────┐
│ 0 │
└──────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要小表也能拿到样本，请用 &lt;code&gt;RESERVOIR&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="陷阱五system-与-tablesample-system-底层不同"&gt;陷阱五：SYSTEM 与 TABLESAMPLE SYSTEM 底层不同
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;USING SAMPLE … (system)&lt;/code&gt; 会把采样下推进扫描；而 &lt;code&gt;TABLESAMPLE SYSTEM (x%)&lt;/code&gt; 可能表现为一个独立算子（见下一节），这会直接影响代价。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="explain-揭示了什么下推-vs-事后过滤"&gt;EXPLAIN 揭示了什么：下推 vs 事后过滤
&lt;/h2&gt;&lt;p&gt;这部分解释了后面所有基准结果。对比两个执行计划：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;system&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌───────────────────────────┐
│ UNGROUPED_AGGREGATE │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ SEQ_SCAN │
│ Sample Method: │
│ System: 10.0% │ &amp;lt;-- 采样发生在扫描算子内部
└───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bernoulli&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌───────────────────────────┐
│ UNGROUPED_AGGREGATE │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ STREAMING_SAMPLE │ &amp;lt;-- 扫描之上多了一个算子
│ Bernoulli: 10.000000% │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ SEQ_SCAN │ &amp;lt;-- 仍然读取每一行
└───────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;两个世界：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SYSTEM&lt;/code&gt;&lt;/strong&gt; 把采样&lt;strong&gt;下推进扫描算子&lt;/strong&gt;，扫描知道可以跳过整块，因此能省 I/O。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;BERNOULLI&lt;/code&gt;&lt;/strong&gt;（和 &lt;code&gt;RESERVOIR&lt;/code&gt;）在扫描&lt;strong&gt;之上&lt;/strong&gt;加一个算子，扫描仍然碰每一行，算子再把大部分行丢掉。你付了全部 I/O 和物化成本，最后扔掉绝大部分。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这一个事实解释了为什么 &lt;code&gt;BERNOULLI&lt;/code&gt; 常常比&amp;quot;不采样&amp;quot;还慢。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="让样本可复现"&gt;让样本可复现
&lt;/h2&gt;&lt;p&gt;随机采样每次结果都不同。要固定下来 —— 这是可复现实验的前提 —— 传入种子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reservoir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;┌──────────────┐
│ 5000 │
└──────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;同一个种子跑两次得到完全相同的 5000 行；换种子则不同。这是让&amp;quot;采样结果&amp;quot;变成可引用产物的唯一办法。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="基准测试采样什么时候真的更快"&gt;基准测试：采样什么时候真的更快
&lt;/h2&gt;&lt;p&gt;以下均为 best-of-3，DuckDB 1.5.4，4 线程，本地 SSD，Parquet + ZSTD。&lt;/p&gt;
&lt;h3 id="廉价谓词500-万行"&gt;廉价谓词（500 万行）
&lt;/h3&gt;&lt;p&gt;数据：5,000,000 行，约 45 MB Parquet。查询：&lt;code&gt;count(*) WHERE value &amp;gt; 900&lt;/code&gt;。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;计划&lt;/th&gt;
 &lt;th&gt;耗时&lt;/th&gt;
 &lt;th&gt;相对全表扫描&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;全表扫描&lt;/td&gt;
 &lt;td&gt;62.9 ms&lt;/td&gt;
 &lt;td&gt;1.00×&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;USING SAMPLE 10% (system)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;55.4 ms&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;1.14× 更快&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;USING SAMPLE 10% (bernoulli)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;160.9 ms&lt;/td&gt;
 &lt;td&gt;0.39×（慢 2.6 倍）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;USING SAMPLE 500000 ROWS (reservoir)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1008.9 ms&lt;/td&gt;
 &lt;td&gt;0.06×（&lt;strong&gt;慢 16 倍&lt;/strong&gt;）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;采样几乎没帮上忙，而且三种方法里有两种&lt;strong&gt;明显更慢&lt;/strong&gt;。&lt;code&gt;RESERVOIR&lt;/code&gt; 不仅要扫全表，还要维护一个 50 万行的蓄水池。&lt;/p&gt;
&lt;h3 id="昂贵的逐行谓词"&gt;昂贵的逐行谓词
&lt;/h3&gt;&lt;p&gt;同样数据，但过滤条件换成逐行正则（对 email 列做 &lt;code&gt;regexp_matches&lt;/code&gt;）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;计划&lt;/th&gt;
 &lt;th&gt;耗时&lt;/th&gt;
 &lt;th&gt;相对全表扫描&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;全表扫描&lt;/td&gt;
 &lt;td&gt;1086.0 ms&lt;/td&gt;
 &lt;td&gt;1.00×&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;USING SAMPLE 10% (system)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;624.5 ms&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;1.7× 更快&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;USING SAMPLE 1% (system)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;390.3 ms&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;2.8× 更快&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;USING SAMPLE 10% (bernoulli)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1170.7 ms&lt;/td&gt;
 &lt;td&gt;0.93×（更慢）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;结论反转了。当每行处理很贵时，&lt;strong&gt;在昂贵操作之前&lt;/strong&gt;丢掉行就有收益 —— 而功劳属于 &lt;code&gt;SYSTEM&lt;/code&gt;，因为它在扫描阶段剪枝，而不是事后过滤。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;由此得到一条经验法则：&lt;/strong&gt; 采样带来的收益，与查询&lt;em&gt;其余部分&lt;/em&gt;的昂贵程度成正比。如果查询只是对本地热数据做一个廉价聚合，全表扫描本来就够快，采样只会增加开销。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="样本有多准"&gt;样本有多准？
&lt;/h2&gt;&lt;p&gt;只有当估计值可信时，采样才有用。对约 499,441 / 5,000,000 行（约 10%）的谓词，把样本放大后的误差如下：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;方法&lt;/th&gt;
 &lt;th&gt;样本比例&lt;/th&gt;
 &lt;th&gt;估计总数&lt;/th&gt;
 &lt;th&gt;误差&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;SYSTEM&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1%&lt;/td&gt;
 &lt;td&gt;565,400&lt;/td&gt;
 &lt;td&gt;13.21%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;SYSTEM&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;5%&lt;/td&gt;
 &lt;td&gt;511,460&lt;/td&gt;
 &lt;td&gt;2.41%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;SYSTEM&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;10%&lt;/td&gt;
 &lt;td&gt;479,750&lt;/td&gt;
 &lt;td&gt;3.94%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;SYSTEM&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;25%&lt;/td&gt;
 &lt;td&gt;511,548&lt;/td&gt;
 &lt;td&gt;2.42%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;BERNOULLI&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1%&lt;/td&gt;
 &lt;td&gt;516,800&lt;/td&gt;
 &lt;td&gt;3.48%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;BERNOULLI&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;10%&lt;/td&gt;
 &lt;td&gt;499,280&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;0.03%&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;1% 太粗&lt;/strong&gt;，不足以引用；&lt;code&gt;SYSTEM&lt;/code&gt; 的误差达到 13%。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;采样误差并不单调&lt;/strong&gt;。这里 &lt;code&gt;SYSTEM&lt;/code&gt; 10% 反而不如 5% —— 按块量化意味着&amp;quot;比例更大&amp;quot;不保证更接近真值。需要给出数字时，&lt;code&gt;BERNOULLI&lt;/code&gt; 是更稳定的估计器。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="采样-vs-近似聚合函数"&gt;采样 vs 近似聚合函数
&lt;/h3&gt;&lt;p&gt;到底该用采样，还是用 &lt;code&gt;approx_*&lt;/code&gt; 聚合函数？在 500 万行上：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;方案&lt;/th&gt;
 &lt;th&gt;耗时&lt;/th&gt;
 &lt;th&gt;结果&lt;/th&gt;
 &lt;th&gt;误差&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;count(DISTINCT id)&lt;/code&gt;（精确）&lt;/td&gt;
 &lt;td&gt;312.6 ms&lt;/td&gt;
 &lt;td&gt;5,000,000&lt;/td&gt;
 &lt;td&gt;—&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;approx_count_distinct(id)&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;139.6 ms&lt;/td&gt;
 &lt;td&gt;4,889,466&lt;/td&gt;
 &lt;td&gt;2.21%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;USING SAMPLE 5% (system)&lt;/code&gt; 放大&lt;/td&gt;
 &lt;td&gt;81.2 ms&lt;/td&gt;
 &lt;td&gt;4,751,360&lt;/td&gt;
 &lt;td&gt;4.97%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这里采样反而&lt;strong&gt;更快&lt;/strong&gt;，但误差大约翻倍。两者并非谁绝对更优：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;approx_*&lt;/code&gt; 聚合函数&lt;/strong&gt;单趟扫描、原地替换精确算子，误差有界、可预期。去重计数、分位数、top-k 优先用它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;采样&lt;/strong&gt;适合&amp;quot;想要一个真实的行的子集、并在这个子集上跑任意后续查询&amp;quot;的场景 —— 比如测试夹具，或可复现的探索样本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;分位数上差距缩小：同样数据下 &lt;code&gt;quantile_cont&lt;/code&gt; 428.6 ms，&lt;code&gt;approx_quantile&lt;/code&gt; 308.1 ms —— 1.4× 的收益，而去重计数是 2.2×。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="什么时候该采样什么时候不该"&gt;什么时候该采样，什么时候不该
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;该采样：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据在远端（S3/HTTP）或处于冷缓存，跳过 I/O 本身就是目的。&lt;/li&gt;
&lt;li&gt;你在探索一张陌生的表，只想要个大概形状，不要精确数字。&lt;/li&gt;
&lt;li&gt;过滤之后的逐行处理很重（正则、JSON 解析、字符串操作）。&lt;/li&gt;
&lt;li&gt;你需要一个可复现的实验 —— 用固定种子。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;不该采样：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全表扫描本来就快（本地热 Parquet、廉价聚合），经常会亏。&lt;/li&gt;
&lt;li&gt;你需要精确答案，采样给不了。&lt;/li&gt;
&lt;li&gt;你想用大行数的 &lt;code&gt;RESERVOIR&lt;/code&gt; —— 它是最贵的方法。&lt;/li&gt;
&lt;li&gt;小表需要保证非空样本（&lt;code&gt;SYSTEM&lt;/code&gt; 可能返回零行）。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="实用配方"&gt;实用配方
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;可复现的 5% 报告样本：&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;read_parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;events.parquet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;精确 N 行的测试夹具：&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;big_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ROWS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reservoir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;fixture.parquet&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FORMAT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PARQUET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;快速估个分布，再验证：&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 先快速看一眼
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SAMPLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bernoulli&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 数字重要时，用不带采样的精确答案
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;p&gt;DuckDB 提供&lt;strong&gt;两种语法&lt;/strong&gt;（&lt;code&gt;USING SAMPLE&lt;/code&gt;、&lt;code&gt;TABLESAMPLE&lt;/code&gt;）和&lt;strong&gt;三种方法&lt;/strong&gt;（&lt;code&gt;SYSTEM&lt;/code&gt;、&lt;code&gt;BERNOULLI&lt;/code&gt;、&lt;code&gt;RESERVOIR&lt;/code&gt;），默认值与代价各不相同。只有 &lt;strong&gt;&lt;code&gt;SYSTEM&lt;/code&gt; 能下推进扫描&lt;/strong&gt;；&lt;code&gt;BERNOULLI&lt;/code&gt; 与 &lt;code&gt;RESERVOIR&lt;/code&gt; 都会先读完每一行，这正是它们常常比全表扫描更慢的原因。采样&lt;strong&gt;不是通用的加速手段&lt;/strong&gt; —— 它在昂贵的逐行谓词上取胜（最高 2.8×），在廉价查询上则可能惨败（低至 0.06×）。&lt;/p&gt;
&lt;p&gt;下一步建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对任何采样查询跑一次 &lt;code&gt;EXPLAIN&lt;/code&gt;，确认采样位于扫描&lt;strong&gt;内部&lt;/strong&gt;，还是位于其上的 &lt;code&gt;STREAMING_SAMPLE&lt;/code&gt; 算子里&lt;/li&gt;
&lt;li&gt;只想要近似数字时优先用 &lt;code&gt;approx_*&lt;/code&gt; 聚合函数；想要&amp;quot;可复跑的行子集&amp;quot;时才用采样&lt;/li&gt;
&lt;li&gt;打算引用某个样本，就一定要设种子&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先测量，再采样。一行 &lt;code&gt;USING SAMPLE 10%&lt;/code&gt; 既可能让查询快 3 倍，也可能慢 16 倍 —— 而 &lt;code&gt;EXPLAIN&lt;/code&gt; 会在你运行之前就告诉你答案。&lt;/p&gt;</description></item></channel></rss>