<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>工作杂谈 on My Learning Notes</title><link>https://eleanora-lyh.github.io/MyLearningNotes/categories/%E5%B7%A5%E4%BD%9C%E6%9D%82%E8%B0%88/</link><description>Recent content in 工作杂谈 on My Learning Notes</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Wed, 02 Sep 2026 10:27:13 +0000</lastBuildDate><atom:link href="https://eleanora-lyh.github.io/MyLearningNotes/categories/%E5%B7%A5%E4%BD%9C%E6%9D%82%E8%B0%88/index.xml" rel="self" type="application/rss+xml"/><item><title>【工作杂谈_20260901】数据监控：ADTK Python 项目搭建与验证</title><link>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/utils/20260901_%E6%95%B0%E6%8D%AE%E7%9B%91%E6%8E%A7_adtk%E9%A1%B9%E7%9B%AE%E6%90%AD%E5%BB%BA%E4%B8%8E%E9%AA%8C%E8%AF%81/</link><pubDate>Tue, 01 Sep 2026 17:20:17 +0800</pubDate><guid>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/utils/20260901_%E6%95%B0%E6%8D%AE%E7%9B%91%E6%8E%A7_adtk%E9%A1%B9%E7%9B%AE%E6%90%AD%E5%BB%BA%E4%B8%8E%E9%AA%8C%E8%AF%81/</guid><description>&lt;p&gt;本文记录一套已经验证成功的 ADTK 项目搭建流程，适用于 Windows、PowerShell 与 VS Code。各步骤给出具体操作、采用原因和成功判据，可以直接从零复现。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;验证日期：2026-09-01&lt;br&gt;
操作系统：Windows&lt;br&gt;
工作区根目录：&lt;code&gt;BigDataLearningNotes&lt;/code&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;开始前需要安装 Python 3.12、Python Launcher 和 VS Code 的 Python 扩展。下面的 PowerShell 命令均在项目根目录中执行，并显式调用虚拟环境解释器，因此不要求预先激活虚拟环境。&lt;/p&gt;
&lt;h1 id="1-最终项目状态"&gt;1. 最终项目状态
&lt;/h1&gt;&lt;p&gt;项目最终使用独立的 Python 虚拟环境运行 ADTK 示例：&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;&lt;code&gt;venv&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;隔离本项目与系统 Python 的依赖&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Python&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;3.12.10&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;兼顾 Python 3.12 支持和 ADTK 旧依赖的兼容性&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;解释器&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;.venv\Scripts\python.exe&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;安装依赖、运行脚本和 VS Code 分析统一使用该解释器&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ADTK&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.6.2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;当前 PyPI 上使用的 ADTK 版本&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;示例入口&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;examples\basic_detection.py&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;生成合成时序、检测异常并绘图&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;图片输出&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;artifacts\basic_detection.png&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;保存检测结果，便于人工检查&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;adtk&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.6.2&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;numpy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1.26.4&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pandas&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;2.3.3&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;scikit-learn&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;1.5.2&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;statsmodels&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0.14.6&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;matplotlib&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;3.10.9&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id="2-从零搭建与验证"&gt;2. 从零搭建与验证
&lt;/h1&gt;&lt;h2 id="步骤-1检查工作区并确定项目根目录"&gt;步骤 1：检查工作区并确定项目根目录
&lt;/h2&gt;&lt;p&gt;在 VS Code 中直接打开 &lt;code&gt;BigDataLearningNotes&lt;/code&gt; 文件夹，并在终端中确认当前位置和 Python 3.12：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Get-Location
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py -0p
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py &lt;span style="color:#ae81ff"&gt;-3.12&lt;/span&gt; --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;py -0p&lt;/code&gt; 应列出 Python 3.12 的安装路径，最后一条命令应输出 &lt;code&gt;Python 3.12.x&lt;/code&gt;。如果列表中没有 3.12，需要先安装对应版本并启用 Python Launcher。当前项目验证时使用的是 &lt;code&gt;Python 3.12.10&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;直接把当前文件夹作为项目根目录，不再嵌套一层同名目录。&lt;/p&gt;
&lt;p&gt;这样做的原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VS Code 打开当前文件夹后，可以直接识别 &lt;code&gt;.venv&lt;/code&gt;、&lt;code&gt;requirements.txt&lt;/code&gt; 和示例代码。&lt;/li&gt;
&lt;li&gt;运行命令时路径更短，输出目录也固定在项目根目录下。&lt;/li&gt;
&lt;li&gt;避免形成 &lt;code&gt;BigDataLearningNotes/BigDataLearningNotes/...&lt;/code&gt; 之类的重复结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;明确指定 Python 3.12，是为了避免误用本机的 Python 3.13，并让虚拟环境与本文验证过的版本保持一致。&lt;/p&gt;
&lt;p&gt;最终目录结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;BigDataLearningNotes/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── .venv/ # 本地虚拟环境，不提交到 Git
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── .vscode/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ └── settings.json # 当前工作区的 VS Code 设置
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── artifacts/ # 示例生成的图片，不提交到 Git
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── examples/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;│ └── basic_detection.py # ADTK 基础检测示例
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── .gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── ADTK_PROJECT_SETUP.md # 本文档
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;├── README.md # 项目使用入口
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;└── requirements.txt # 可复现依赖约束
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;成功判据：&lt;code&gt;Get-Location&lt;/code&gt; 指向项目根目录，并且 &lt;code&gt;py -3.12 --version&lt;/code&gt; 能正常输出版本。&lt;/p&gt;
&lt;h2 id="步骤-2创建并确认虚拟环境"&gt;步骤 2：创建并确认虚拟环境
&lt;/h2&gt;&lt;p&gt;通过 VS Code 的 Python 环境工具在项目根目录创建 &lt;code&gt;.venv&lt;/code&gt;。等价的 PowerShell 命令是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py &lt;span style="color:#ae81ff"&gt;-3.12&lt;/span&gt; -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;虚拟环境创建后，直接使用其中的解释器确认版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;输出应为 &lt;code&gt;Python 3.12.x&lt;/code&gt;，本项目验证时为 &lt;code&gt;Python 3.12.10&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这样做的原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ADTK 0.6.2 发布较早，先在独立环境中验证不会污染系统 Python。&lt;/li&gt;
&lt;li&gt;机器上还存在 Python 3.13，最初编辑器可能选中系统解释器；项目实际需要统一使用 &lt;code&gt;.venv&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;依赖安装、编辑器类型分析和脚本运行使用同一解释器，可以避免“终端能运行但编辑器报无法导入”或相反的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;后续命令都显式调用 &lt;code&gt;.\.venv\Scripts\python.exe&lt;/code&gt;，不依赖终端中的 &lt;code&gt;python&lt;/code&gt; 或虚拟环境激活状态。这样即使系统 &lt;code&gt;PATH&lt;/code&gt; 指向其他 Python，也不会安装到错误的环境。&lt;/p&gt;
&lt;p&gt;在 VS Code 中确认解释器的方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;按 &lt;code&gt;Ctrl+Shift+P&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;执行 &lt;code&gt;Python: Select Interpreter&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;选择 &lt;code&gt;.venv\Scripts\python.exe&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;查看 VS Code 状态栏，确认当前解释器来自项目 &lt;code&gt;.venv&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;本次最终检查表明，VS Code/Pylance 当前选中的解释器已经是项目 &lt;code&gt;.venv&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;成功判据：项目根目录中存在 &lt;code&gt;.venv&lt;/code&gt;，版本检查成功，并且 VS Code 状态栏显示该虚拟环境。&lt;/p&gt;
&lt;h2 id="步骤-3编写兼容的依赖约束"&gt;步骤 3：编写兼容的依赖约束
&lt;/h2&gt;&lt;p&gt;创建 &lt;code&gt;requirements.txt&lt;/code&gt;，最终内容为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;adtk==0.6.2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;matplotlib&amp;gt;=3.8,&amp;lt;3.11
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;numpy&amp;gt;=1.26,&amp;lt;2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pandas&amp;gt;=2.1,&amp;lt;3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;scikit-learn&amp;gt;=1.3,&amp;lt;1.6
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;statsmodels&amp;gt;=0.14,&amp;lt;0.15
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;ADTK 0.6.2 发布于 2020 年，主要依赖没有完整的版本上限。仅执行不带约束的 &lt;code&gt;pip install adtk&lt;/code&gt;，可能会得到远晚于 ADTK 发布的新主版本。&lt;code&gt;pip&lt;/code&gt; 能完成安装并不代表这些包的运行时 API 仍与 ADTK 兼容，因此本项目主动限定经过验证的范围。&lt;/p&gt;
&lt;p&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;adtk==0.6.2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;固定学习目标，避免未来解析到行为不同的版本&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;numpy&amp;gt;=1.26,&amp;lt;2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Python 3.12 需要较新的 NumPy，同时避开 NumPy 2 的大版本兼容风险&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pandas&amp;gt;=2.1,&amp;lt;3&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;保留 Python 3.12 支持，避开 pandas 3 的 API 变化&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;scikit-learn&amp;gt;=1.3,&amp;lt;1.6&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;使用具有 Python 3.12 轮子的版本，并限制在已验证范围内&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;statsmodels&amp;gt;=0.14,&amp;lt;0.15&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;使用支持当前 Python 的 0.14 系列&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;matplotlib&amp;gt;=3.8,&amp;lt;3.11&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Python 3.12 使用新版 Matplotlib，但避开 3.11 删除的 ADTK 依赖 API&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这里使用“版本范围”而不是把所有传递依赖完全锁死，是为了让学习项目在兼容区间内仍能接收补丁版本。上表中的具体版本则是本机已经验证通过的组合。&lt;/p&gt;
&lt;p&gt;这组范围约束用于控制兼容性，并不保证不同日期安装到完全相同的传递依赖。若需要在 CI 或其他机器上精确复现当前环境，可以在验证通过后额外生成锁定文件：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe -m pip freeze | Set-Content requirements-lock.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;之后使用 &lt;code&gt;pip install -r requirements-lock.txt&lt;/code&gt; 安装锁定版本；日常学习仍可继续使用 &lt;code&gt;requirements.txt&lt;/code&gt; 中的兼容范围。&lt;/p&gt;
&lt;p&gt;成功判据：项目根目录存在 &lt;code&gt;requirements.txt&lt;/code&gt;，并且内容包含上述六项约束。&lt;/p&gt;
&lt;h2 id="步骤-4安装项目依赖"&gt;步骤 4：安装项目依赖
&lt;/h2&gt;&lt;p&gt;先升级虚拟环境中的 &lt;code&gt;pip&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe -m pip install --upgrade pip
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;再按照项目约束安装依赖：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe -m pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;使用 &lt;code&gt;.venv&lt;/code&gt; 中的 &lt;code&gt;python.exe -m pip&lt;/code&gt;，可以明确保证依赖安装到当前项目环境，而不是系统 Python。&lt;/p&gt;
&lt;p&gt;可以用下面的命令查看核心包的实际版本：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe -m pip show adtk matplotlib numpy pandas scikit-learn statsmodels
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;实际版本应落在约束范围内；本文验证通过的具体组合见第 1 节。&lt;/p&gt;
&lt;p&gt;成功判据：安装命令正常结束，&lt;code&gt;pip show&lt;/code&gt; 能找到上述六个包，并且版本位于 &lt;code&gt;requirements.txt&lt;/code&gt; 指定的范围内。&lt;/p&gt;
&lt;h3 id="检查依赖冲突"&gt;检查依赖冲突
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe -m pip check
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;结果：&lt;code&gt;No broken requirements found.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;作用：确认当前已安装包之间没有缺失依赖或声明冲突。&lt;/p&gt;
&lt;h2 id="步骤-5创建项目文件"&gt;步骤 5：创建项目文件
&lt;/h2&gt;&lt;p&gt;创建或补充以下文件：&lt;/p&gt;
&lt;h3 id="gitignore"&gt;&lt;code&gt;.gitignore&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;忽略以下本地文件和生成物：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.venv/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;__pycache__/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;*.py[cod]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.pytest_cache/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.mypy_cache/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;artifacts/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;原因是虚拟环境体积较大且与机器相关，缓存和生成图片也可以由代码重新产生，不应提交到 Git。&lt;/p&gt;
&lt;h3 id="readmemd"&gt;&lt;code&gt;README.md&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;提供项目简介、环境要求、安装命令、示例运行命令和后续 ADTK 学习方向。它作为日常使用入口，本文则保留更详细的搭建与排查过程。&lt;/p&gt;
&lt;h3 id="examplesbasic_detectionpy"&gt;&lt;code&gt;examples/basic_detection.py&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;先创建示例目录：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;New-Item -ItemType Directory -Force examples | Out-Null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;examples\basic_detection.py&lt;/code&gt; 的完整代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;35
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;36
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;37
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;38
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;39
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;40
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;41
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;42
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;43
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;44
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;45
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;46
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;47
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;48
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;49
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;50
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;51
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;52
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;53
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;54
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;55
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;56
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;57
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;58
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;59
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;60
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;61
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;62
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;63
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;64
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;65
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;66
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;67
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;68
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;69
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;70
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;71
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;72
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;73
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;74
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;75
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;76
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;77
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;78
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;79
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;80
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;81
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;82
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;83
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;84
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;85
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;86
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;87
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;88
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; pathlib &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Path
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; matplotlib
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Select a non-interactive backend before importing pyplot so the example can&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# save a chart without opening a desktop window.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;matplotlib&lt;span style="color:#f92672"&gt;.&lt;/span&gt;use(&lt;span style="color:#e6db74"&gt;&amp;#34;Agg&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; numpy &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; np
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; pandas &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; pd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; adtk.data &lt;span style="color:#f92672"&gt;import&lt;/span&gt; to_events, validate_series
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; adtk.detector &lt;span style="color:#f92672"&gt;import&lt;/span&gt; InterQuartileRangeAD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; adtk.visualization &lt;span style="color:#f92672"&gt;import&lt;/span&gt; plot
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; matplotlib &lt;span style="color:#f92672"&gt;import&lt;/span&gt; pyplot &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; plt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ADTK 0.6.2 requests the old name for this bundled Matplotlib style.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;seaborn-whitegrid&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; &lt;span style="color:#f92672"&gt;in&lt;/span&gt; plt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;style&lt;span style="color:#f92672"&gt;.&lt;/span&gt;library:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; plt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;style&lt;span style="color:#f92672"&gt;.&lt;/span&gt;library[&lt;span style="color:#e6db74"&gt;&amp;#34;seaborn-whitegrid&amp;#34;&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; plt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;style&lt;span style="color:#f92672"&gt;.&lt;/span&gt;library[
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;seaborn-v0_8-whitegrid&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;build_sample_series&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; tuple[pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series, pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DatetimeIndex]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&amp;#34;Create a regular hourly series with two known outliers.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# A fixed seed makes the generated data and detection result reproducible.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; random_generator &lt;span style="color:#f92672"&gt;=&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;random&lt;span style="color:#f92672"&gt;.&lt;/span&gt;default_rng(seed&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;42&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; index &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;date_range(&lt;span style="color:#e6db74"&gt;&amp;#34;2026-01-01&amp;#34;&lt;/span&gt;, periods&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;24&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt;, freq&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;h&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hours &lt;span style="color:#f92672"&gt;=&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;arange(len(index))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Combine a daily cycle with small random noise to model a normal signal.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; values &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;sin(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; np&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pi &lt;span style="color:#f92672"&gt;*&lt;/span&gt; hours &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; random_generator&lt;span style="color:#f92672"&gt;.&lt;/span&gt;normal(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0.35&lt;/span&gt;, len(index))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Inject one high and one low outlier at known positions for verification.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; anomaly_positions &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;24&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;24&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;18&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; values[anomaly_positions] &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; [&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;, &lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; series &lt;span style="color:#f92672"&gt;=&lt;/span&gt; pd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Series(values, index&lt;span style="color:#f92672"&gt;=&lt;/span&gt;index, name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;metric&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ADTK expects an ordered DatetimeIndex with a valid, regular frequency.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; validate_series(series), index[anomaly_positions]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; series, injected_anomalies &lt;span style="color:#f92672"&gt;=&lt;/span&gt; build_sample_series()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Fit IQR boundaries on the series and return one anomaly flag per point.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; detector &lt;span style="color:#f92672"&gt;=&lt;/span&gt; InterQuartileRangeAD(c&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1.5&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; anomalies &lt;span style="color:#f92672"&gt;=&lt;/span&gt; detector&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fit_detect(series)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Treat any undefined flags as normal before using the result as an index.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; detected_anomalies &lt;span style="color:#f92672"&gt;=&lt;/span&gt; anomalies&lt;span style="color:#f92672"&gt;.&lt;/span&gt;fillna(&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;astype(bool)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; detected_times &lt;span style="color:#f92672"&gt;=&lt;/span&gt; series&lt;span style="color:#f92672"&gt;.&lt;/span&gt;index[detected_anomalies]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Fail loudly if the detector misses either deliberately injected outlier.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; missed_times &lt;span style="color:#f92672"&gt;=&lt;/span&gt; injected_anomalies&lt;span style="color:#f92672"&gt;.&lt;/span&gt;difference(detected_times)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; missed_times&lt;span style="color:#f92672"&gt;.&lt;/span&gt;empty:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;raise&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RuntimeError&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Known anomalies were not detected: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;list(missed_times)&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Detected &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;len(detected_times)&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; anomalous points:&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; timestamp &lt;span style="color:#f92672"&gt;in&lt;/span&gt; detected_times:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;- &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;timestamp&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;series&lt;span style="color:#f92672"&gt;.&lt;/span&gt;loc[timestamp]&lt;span style="color:#e6db74"&gt;:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.2f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Convert point-wise flags into time intervals suitable for alert records.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Anomaly events: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;to_events(anomalies)&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Create the output directory on first run and persist the annotated chart.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Path(&lt;span style="color:#e6db74"&gt;&amp;#34;artifacts/basic_detection.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output_path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;parent&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mkdir(parents&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, exist_ok&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; plot(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; series,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; anomaly&lt;span style="color:#f92672"&gt;=&lt;/span&gt;anomalies,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; anomaly_color&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;red&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; anomaly_tag&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;marker&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; plt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;title(&lt;span style="color:#e6db74"&gt;&amp;#34;ADTK interquartile-range anomaly detection&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; plt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;tight_layout()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; plt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;savefig(output_path, dpi&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;150&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; plt&lt;span style="color:#f92672"&gt;.&lt;/span&gt;close(&lt;span style="color:#e6db74"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Chart saved to &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;output_path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;resolve()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;示例按以下顺序工作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用固定随机种子生成 14 天的小时级周期数据。&lt;/li&gt;
&lt;li&gt;手工注入两个已知异常点。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;validate_series&lt;/code&gt; 校验时间序列格式。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;InterQuartileRangeAD(c=1.5)&lt;/code&gt; 拟合并检测异常。&lt;/li&gt;
&lt;li&gt;检查两个已知异常是否都被发现；若漏检则主动抛出异常。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;to_events&lt;/code&gt; 把布尔异常序列转换为事件。&lt;/li&gt;
&lt;li&gt;用 ADTK 的 &lt;code&gt;plot&lt;/code&gt; 绘图并保存到 &lt;code&gt;artifacts\basic_detection.png&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;选择合成数据的原因：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不依赖网络和外部数据文件，任何机器都能直接运行。&lt;/li&gt;
&lt;li&gt;已知异常点的位置，可对检测结果做自动断言，而不是只凭肉眼判断。&lt;/li&gt;
&lt;li&gt;固定随机种子后结果可重复，便于修改检测器参数并比较变化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;脚本使用 Matplotlib 的 &lt;code&gt;Agg&lt;/code&gt; 后端，因为示例的目标是保存图片，不要求弹出桌面窗口。这也更适合自动化验证。&lt;/p&gt;
&lt;p&gt;ADTK 0.6.2 使用旧 Matplotlib 样式名 &lt;code&gt;seaborn-whitegrid&lt;/code&gt;，当前示例在旧名称不存在时，把它映射到新版名称 &lt;code&gt;seaborn-v0_8-whitegrid&lt;/code&gt;。这个兼容处理让代码仍然调用 ADTK 原生的 &lt;code&gt;plot&lt;/code&gt;，同时能够使用当前验证过的 Matplotlib。&lt;/p&gt;
&lt;p&gt;脚本通过 &lt;code&gt;output_path.parent.mkdir(parents=True, exist_ok=True)&lt;/code&gt; 自动创建 &lt;code&gt;artifacts&lt;/code&gt; 目录，因此第一次运行前不需要手工创建输出目录。&lt;/p&gt;
&lt;p&gt;成功判据：&lt;code&gt;.gitignore&lt;/code&gt;、&lt;code&gt;README.md&lt;/code&gt; 和 &lt;code&gt;examples\basic_detection.py&lt;/code&gt; 均已存在，示例文件包含数据生成、检测、结果断言和绘图逻辑。&lt;/p&gt;
&lt;h2 id="步骤-6执行语法检查"&gt;步骤 6：执行语法检查
&lt;/h2&gt;&lt;p&gt;正式运行前，先检查示例能否被 Python 正确编译：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe -m py_compile examples\basic_detection.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;语法检查成本很低，可以先排除缩进、括号和其他语法错误，再把完整运行用于验证依赖和程序行为。命令没有输出且正常返回，就表示检查通过。&lt;/p&gt;
&lt;p&gt;成功判据：命令退出码为 &lt;code&gt;0&lt;/code&gt;，终端中没有错误信息。&lt;/p&gt;
&lt;h2 id="步骤-7端到端运行验证"&gt;步骤 7：端到端运行验证
&lt;/h2&gt;&lt;p&gt;执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe examples\basic_detection.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;最终结果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ADTK 成功导入。&lt;/li&gt;
&lt;li&gt;两个注入的异常点都被检测到。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;to_events&lt;/code&gt; 成功生成异常事件。&lt;/li&gt;
&lt;li&gt;图片成功保存到 &lt;code&gt;artifacts\basic_detection.png&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;图片中的异常点以红色标记显示，位置与注入点一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;运行输出为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Detected 2 anomalous points:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- 2026-01-05 06:00:00: 33.88
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- 2026-01-11 18:00:00: 5.95
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Anomaly events: [(Timestamp(&amp;#39;2026-01-05 06:00:00&amp;#39;), Timestamp(&amp;#39;2026-01-05 06:59:59.999999999&amp;#39;)), (Timestamp(&amp;#39;2026-01-11 18:00:00&amp;#39;), Timestamp(&amp;#39;2026-01-11 18:59:59.999999999&amp;#39;))]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Chart saved to &amp;lt;项目根目录&amp;gt;\artifacts\basic_detection.png
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;检测结果如下：&lt;/p&gt;
&lt;p&gt;&lt;img alt="ADTK IQR 检测器标记出的两个异常点" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="C:/Users/v-liuyuhang/source/repos/MyPro/MyLearningNotes/content/posts/Work/Utils/assets/2026-09-01-17-20-17-image.png"&gt;&lt;/p&gt;
&lt;p&gt;一次完整运行会同时验证 NumPy 和 pandas 数据构造、ADTK 导入、序列校验、检测器拟合、异常事件转换、Matplotlib 绘图以及文件写入路径。&lt;/p&gt;
&lt;p&gt;成功判据：程序退出码为 &lt;code&gt;0&lt;/code&gt;，终端列出两个异常时间，并且生成的图片中有两个红色异常标记。&lt;/p&gt;
&lt;h1 id="3-一次性复查"&gt;3. 一次性复查
&lt;/h1&gt;&lt;p&gt;完成搭建后，可以在项目根目录依次执行以下命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe --version
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe -m pip check
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe -m py_compile examples\basic_detection.py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\python.exe examples\basic_detection.py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Test-Path .\artifacts\basic_detection.png
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;pip check&lt;/code&gt; 应输出 &lt;code&gt;No broken requirements found.&lt;/code&gt;，语法检查应无错误，示例应列出两个注入的异常时间，最后的 &lt;code&gt;Test-Path&lt;/code&gt; 应返回 &lt;code&gt;True&lt;/code&gt;。此外，还应确认 VS Code/Pylance 选择了 &lt;code&gt;.venv\Scripts\python.exe&lt;/code&gt;，且 &lt;code&gt;.venv&lt;/code&gt;、缓存文件和生成图片均未被 Git 跟踪。&lt;/p&gt;
&lt;p&gt;至此，ADTK 学习验证项目已经可以稳定运行。后续可以在同一个虚拟环境中继续练习 &lt;code&gt;ThresholdAD&lt;/code&gt;、&lt;code&gt;PersistAD&lt;/code&gt;、&lt;code&gt;SeasonalAD&lt;/code&gt;，或把合成时间序列替换为自己的 CSV 数据。&lt;/p&gt;</description></item><item><title>【工作杂谈_20260825】数据平台与工程治理: Benchmark 与 Baseline：从基准测试到生产基线</title><link>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/dataplatformengineering/20260825_%E6%95%B0%E6%8D%AE%E5%B9%B3%E5%8F%B0%E4%B8%8E%E5%B7%A5%E7%A8%8B%E6%B2%BB%E7%90%86_benchmark%E4%B8%8Ebaseline/</link><pubDate>Tue, 25 Aug 2026 19:14:25 +0800</pubDate><guid>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/dataplatformengineering/20260825_%E6%95%B0%E6%8D%AE%E5%B9%B3%E5%8F%B0%E4%B8%8E%E5%B7%A5%E7%A8%8B%E6%B2%BB%E7%90%86_benchmark%E4%B8%8Ebaseline/</guid><description>&lt;h1 id="1-benchmark-与-baseline-分别是什么"&gt;1. Benchmark 与 Baseline 分别是什么
&lt;/h1&gt;&lt;p&gt;在性能工程和大数据平台建设中，Benchmark 与 Baseline 经常同时出现，但它们并不处于同一个层次：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Benchmark（基准测试）是一把定义了使用方法的“尺子”，Baseline（基线）是团队选定并保存下来的“参考刻度”。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Benchmark&lt;/strong&gt; 主要回答：在什么数据、负载、资源和执行规则下，系统表现如何？&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Baseline&lt;/strong&gt; 主要回答：这次结果相对某个已知状态，是改善、退化，还是异常？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如，团队固定使用 1 TB 数据、20 条 SQL、相同计算资源和 10 个并发用户测试两个查询引擎，这套测试协议和执行过程是 Benchmark。旧版本测得的 &lt;code&gt;P95 = 8.2 s&lt;/code&gt; 被批准为后续版本的比较起点，这个值才是 Baseline。&lt;/p&gt;
&lt;p&gt;实际交流中，英文 &lt;code&gt;benchmark&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;Benchmark specification&lt;/td&gt;
 &lt;td&gt;测试协议：数据、负载、环境、步骤和指标&lt;/td&gt;
 &lt;td&gt;“1 TB 数据、并发 10、预热 2 次、正式运行 5 次”&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Benchmark run/result&lt;/td&gt;
 &lt;td&gt;某次执行及其测量结果&lt;/td&gt;
 &lt;td&gt;“本次 P95 查询延迟为 8.2 秒”&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Baseline&lt;/td&gt;
 &lt;td&gt;被接受并保存的参考值或参考范围&lt;/td&gt;
 &lt;td&gt;“发布版本 v3.2 的 P95 Baseline 为 8.2 秒”&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Target / SLO&lt;/td&gt;
 &lt;td&gt;希望达到或承诺达到的目标&lt;/td&gt;
 &lt;td&gt;“P95 必须小于 6 秒”&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Baseline 描述的是参照状态，Target/SLO 描述的是期望状态。&lt;/strong&gt; Baseline 可以低于目标，也可能已经不满足目标，不能把二者混为一谈。&lt;/p&gt;
&lt;h2 id="11-benchmark基准测试"&gt;1.1 Benchmark（基准测试）
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;定义&lt;/strong&gt;：一套可重复执行的测试协议或工作负载，用于度量系统或组件在明确条件下的性能、容量、稳定性、正确性和成本。它包括测试方法，也包括承载该方法的数据集、脚本与工具。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：测试目标、数据集、负载模型、运行环境、操作步骤和度量口径都必须明确并尽量保持一致。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;举例&lt;/strong&gt;：TPC-H 的 22 条 SQL 查询；HiBench 中的 WordCount、K-Means 作业；JMeter 压测脚本。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用途&lt;/strong&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;技术选型&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;选 Spark 还是 Flink？选 ClickHouse 还是 Doris？跑一套 Benchmark 用数据说话。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;版本升级验证&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;从 Spark 2.x 升到 3.x，性能是提升还是回退？用 Benchmark 量化对比。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;容量规划&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;数据量翻 10 倍需要加多少机器？通过 Benchmark 推算线性扩展能力。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;参数调优验证&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;改了 JVM 参数或并行度，用 Benchmark 确认是否真的有提升。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;SLA 定义&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;承诺客户 99% 查询 &amp;lt; 2 秒，需要先 Benchmark 验证平台能力。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;硬件/云厂商评估&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;不同机型（如计算型 vs 内存型）跑同样任务，选性价比最高的。&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="111-常见的-benchmark-工具与标准"&gt;1.1.1 常见的 Benchmark 工具与标准
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&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;strong&gt;TPC-H / TPC-DS&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;决策支持/数仓查询性能（SQL 复杂度高）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;HiBench&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;Hadoop/Spark 综合套件（含 WordCount、Sort、SQL 等）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;TPCx-HS&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;Hadoop 系统整体性能&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;YCSB&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;NoSQL 数据库（HBase、Cassandra）的读写性能&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;TPCx-BB&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;大数据基准测试（BigBench，覆盖结构化+半结构化）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Sysbench&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;底层系统（CPU、内存、磁盘 IO）性能&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;标准套件保证了工作负载有共同语言，但不等于真实生产负载。正式选型时通常需要同时运行“标准套件”和“从生产中抽取的代表性任务”。如果对外宣称官方 TPC 成绩，还需要遵守对应规范的审计和披露要求；内部跑了相似 SQL，不应直接称为官方 TPC 成绩。&lt;/p&gt;
&lt;h3 id="112-benchmark-在项目生命周期中的位置"&gt;1.1.2 Benchmark 在项目生命周期中的位置
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;架构设计 → 技术选型（Benchmark ①）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;开发完成 → 性能测试（Benchmark ②）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;预上线 → 容量规划（Benchmark ③）
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;生产运行 → 建立 Baseline → 日常监控告警
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;版本迭代 → 回归测试（Benchmark ④）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它是&lt;strong&gt;贯穿始终&lt;/strong&gt;的活动，而不是只在某个阶段做一次。&lt;/p&gt;
&lt;h2 id="12-baseline基线"&gt;1.2 Baseline（基线）
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义&lt;/strong&gt;：一个已经确认、可用于后续比较的&lt;strong&gt;数值、范围或状态&lt;/strong&gt;。它既可以来自某次受控 Benchmark，也可以来自一段稳定的生产历史。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点&lt;/strong&gt;：是一个具体的数字（如“单节点排序耗时 120 秒”）或一组指标（如“P99 延迟 50ms”）。它不一定是固定值，也可能是动态范围。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;举例&lt;/strong&gt;：优化前的“旧版本 Flink 作业吞吐量 10 万条/秒”；生产环境的“正常 CPU 使用率 40%”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用途&lt;/strong&gt;：
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;量化改进&lt;/strong&gt;：只有知道优化前是多少，才能说“提升了 30%”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常告警&lt;/strong&gt;：当线上指标偏离 Baseline 超过阈值时触发报警（例如 CPU 突然飙升到 80%，而 Baseline 是 40%）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标设定&lt;/strong&gt;：团队可以约定“新版本必须达到 Baseline 的 1.2 倍性能”作为交付标准。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;历史追溯&lt;/strong&gt;：排查问题时能回看 Baseline，确认是从哪个版本开始变慢的。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个可用的 Baseline 不能只保存孤立的数字，还要保存它成立的上下文：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Baseline = 指标值/范围
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; + 工作负载与数据规模
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; + 软件版本与配置
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; + 硬件和运行环境
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; + 统计口径与样本窗口
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; + 生效时间与负责人
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;否则，“P95 延迟为 50 ms”无法说明是在单并发还是百并发、热缓存还是冷缓存、哪个版本和哪种数据规模下得到的，也就无法公平比较。&lt;/p&gt;
&lt;h2 id="13-区别"&gt;1.3 区别
&lt;/h2&gt;&lt;p&gt;在大数据场景里，&lt;strong&gt;Baseline（基线）和 Benchmark（基准测试/标杆）&lt;/strong&gt; 都用于比较，但比较对象不同：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Baseline 主要回答“相对自己过去有没有异常或改善？”&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Benchmark 主要回答“在统一条件下，系统能力到底怎么样，和目标或其他方案相比如何？”&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;公开资料通常也把 Baseline 定义为衡量变化的起点，而 Benchmark 更多使用行业标准、竞争对象或统一测试标准作为参照。 &lt;a class="link" href="https://www.usertesting.com/blog/research-benchmarking-vs-baselines" target="_blank" rel="noopener"
 &gt;[usertesting.com]&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;维度&lt;/th&gt;
 &lt;th&gt;Benchmark&lt;/th&gt;
 &lt;th&gt;Baseline&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;本质&lt;/strong&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;strong&gt;核心问题&lt;/strong&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;strong&gt;主要组成&lt;/strong&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;strong&gt;典型用途&lt;/strong&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;strong&gt;变化方式&lt;/strong&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;strong&gt;举例&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;“使用固定 1 TB 数据运行 TPC-DS 类查询”&lt;/td&gt;
 &lt;td&gt;“当前发布版本的 P95 是 8.2 秒”&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;常见误区&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;只跑一次、只看平均值，或在不同环境中比较&lt;/td&gt;
 &lt;td&gt;把 Baseline 当成目标、极限值，或发现异常后立即覆盖&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="14-关系"&gt;1.4 关系
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Benchmark 结果经过正确性校验、重复性检查和团队确认后，&lt;strong&gt;可以被选为测试 Baseline&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;生产系统稳定运行一段时间后，也可以根据历史分位数或动态区间建立&lt;strong&gt;生产 Baseline&lt;/strong&gt;，不要求先跑标准 Benchmark。&lt;/li&gt;
&lt;li&gt;没有历史 Baseline 时，Benchmark 仍然可以横向比较候选方案，或直接与 Target/SLO 比较。&lt;/li&gt;
&lt;li&gt;有 Baseline 但不重复执行相同测量，也无法判断变化。因此，真正有价值的是“可重复的 Benchmark + 有上下文的 Baseline”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际工作中常说的“打 Baseline”，通常是指执行一轮受控测试，确认结果有效后，将它登记为后续版本的比较起点。&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 S["Benchmark 协议"] --&gt; R["执行并得到结果"]
 R --&gt; V{"正确且可重复？"}
 V --&gt;|否| F["修正测试或系统"]
 V --&gt;|是| B["批准为测试 Baseline"]
 B --&gt; N["下一版本重复 Benchmark"]
 N --&gt; C["与 Baseline 和 Target 比较"]&lt;/pre&gt;&lt;h1 id="2-如何设计一个可信的-benchmark"&gt;2. 如何设计一个可信的 Benchmark
&lt;/h1&gt;&lt;p&gt;Benchmark 的可信度不来自脚本跑得多复杂，而来自三个条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可控&lt;/strong&gt;：除待比较变量外，其他关键条件尽量一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可重复&lt;/strong&gt;：其他人根据记录可以重现测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有代表性&lt;/strong&gt;：测试负载能够覆盖真正关心的生产模式。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="21-先定义决策"&gt;2.1 先定义决策
&lt;/h2&gt;&lt;p&gt;开始测试前，先把问题写成可证伪的判断。例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;决策：Spark 3.5 是否可以替换 Spark 3.4？
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;保持不变：数据快照、集群资源、作业代码、配置和执行时段
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;唯一变量：Spark 版本
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;正确性门槛：输出行数和关键指标必须一致
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;性能门槛：核心作业耗时回退不超过 5%
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;成本门槛：每 TB 计算成本回退不超过 10%
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这比“比较两个版本谁更快”更有效，因为它明确了比较对象、控制变量和通过标准。技术选型也一样：不是寻找脱离场景的“最快引擎”，而是判断哪个候选方案更符合当前工作负载和约束。&lt;/p&gt;
&lt;h2 id="22-固定测试契约"&gt;2.2 固定测试契约
&lt;/h2&gt;&lt;p&gt;一份完整的 Benchmark specification 至少要记录下面这些信息：&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;规模、快照、分布、倾斜、文件格式、压缩方式&lt;/td&gt;
 &lt;td&gt;只生成均匀随机数据，掩盖生产倾斜&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;负载&lt;/td&gt;
 &lt;td&gt;查询/作业集合、读写比例、并发数、到达速率&lt;/td&gt;
 &lt;td&gt;只测单条最快 SQL，不测混合负载&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;环境&lt;/td&gt;
 &lt;td&gt;节点数、CPU、内存、磁盘、网络、可用区&lt;/td&gt;
 &lt;td&gt;两个方案实际拿到的资源不同&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;软件&lt;/td&gt;
 &lt;td&gt;引擎、JDK、依赖、配置、镜像和代码版本&lt;/td&gt;
 &lt;td&gt;升级引擎时顺便修改多个参数&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;执行&lt;/td&gt;
 &lt;td&gt;冷/热缓存、预热次数、重复次数、运行顺序&lt;/td&gt;
 &lt;td&gt;A 永远先跑，结果受时段影响&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;指标&lt;/td&gt;
 &lt;td&gt;P50/P95/P99、吞吐量、错误率、资源和成本&lt;/td&gt;
 &lt;td&gt;只看平均耗时，忽略长尾与失败请求&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以把这些内容版本化保存为测试清单：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;benchmark_id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;sql-engine-1tb-v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;dataset&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;sales_snapshot_2026_08_20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;data_size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1TB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;workload&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;decision_support_queries_v3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;concurrency&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;environment&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5_nodes_16c64g&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;cache_mode&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;warm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;warmup_runs&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;measured_runs&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metrics&lt;/span&gt;: [&lt;span style="color:#ae81ff"&gt;p50_latency, p95_latency, throughput, error_rate, cost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;只要数据、负载或环境发生了会影响可比性的变化，就应该生成新的协议版本，而不是悄悄覆盖旧定义。&lt;/p&gt;
&lt;h2 id="23-让负载接近真实生产"&gt;2.3 让负载接近真实生产
&lt;/h2&gt;&lt;p&gt;标准套件适合建立共同语言，生产样本适合回答自己的问题。两者通常需要结合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 TPC-DS、YCSB 或 HiBench 覆盖标准化能力。&lt;/li&gt;
&lt;li&gt;从生产日志中抽取高频、关键和长尾任务，形成业务工作负载。&lt;/li&gt;
&lt;li&gt;保留真实的数据分布、热点 Key、小文件比例和数据倾斜。&lt;/li&gt;
&lt;li&gt;按高峰期重放并发与请求到达节奏，而不是只做串行测试。&lt;/li&gt;
&lt;li&gt;同时覆盖正常路径、资源紧张和故障恢复，但把不同场景分开报告。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;性能比较之前必须先校验正确性。一个因为少处理了数据而更快的方案不是优化。常见校验包括输出行数、关键聚合值、空值率、主键重复数，以及文件或结果集校验和。&lt;/p&gt;
&lt;h2 id="24-执行和统计结果"&gt;2.4 执行和统计结果
&lt;/h2&gt;&lt;p&gt;一次相对可靠的执行流程是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;清理或明确缓存状态，并执行预热轮次。&lt;/li&gt;
&lt;li&gt;按预先规定的次数重复测试；A/B 交替或随机运行顺序，降低时段噪声。&lt;/li&gt;
&lt;li&gt;每次只改变一个待验证因素，其他配置保持一致。&lt;/li&gt;
&lt;li&gt;保存每轮原始结果，不只保存最终平均值。&lt;/li&gt;
&lt;li&gt;报告中位数和 P95/P99 等分位数，同时报告错误率与波动范围。&lt;/li&gt;
&lt;li&gt;保存执行日志、引擎指标、资源利用率、配置快照和代码版本。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&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;越低越好&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;(current - baseline) / baseline&lt;/code&gt; 大于 0 表示回退&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;吞吐量、每秒处理行数&lt;/td&gt;
 &lt;td&gt;越高越好&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;(current - baseline) / baseline&lt;/code&gt; 大于 0 表示提升&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果 Baseline 作业耗时为 100 分钟，新版本为 112 分钟，则耗时回退 12%。如果吞吐量从每秒 10 万条升到 11.5 万条，则吞吐量提升 15%。不要只写“快了 15%”，必须同时说明指标、方向和计算口径。&lt;/p&gt;
&lt;h2 id="25-结果不是一个孤立排名"&gt;2.5 结果不是一个孤立排名
&lt;/h2&gt;&lt;p&gt;下面是一份简化后的查询引擎评测结果：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;方案&lt;/th&gt;
 &lt;th style="text-align: right"&gt;P50 延迟&lt;/th&gt;
 &lt;th style="text-align: right"&gt;P95 延迟&lt;/th&gt;
 &lt;th style="text-align: right"&gt;吞吐量&lt;/th&gt;
 &lt;th style="text-align: right"&gt;错误率&lt;/th&gt;
 &lt;th style="text-align: right"&gt;每 1,000 次查询成本&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Engine A&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1.8 s&lt;/td&gt;
 &lt;td style="text-align: right"&gt;7.6 s&lt;/td&gt;
 &lt;td style="text-align: right"&gt;42 QPS&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.1%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;18 元&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Engine B&lt;/td&gt;
 &lt;td style="text-align: right"&gt;2.2 s&lt;/td&gt;
 &lt;td style="text-align: right"&gt;5.1 s&lt;/td&gt;
 &lt;td style="text-align: right"&gt;39 QPS&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;14 元&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;不能简单得出“Engine A 更快”。它的中位延迟和吞吐量更好，但 Engine B 的长尾、稳定性和成本更好。最终结论应绑定决策条件，例如：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;在 P95 小于 6 秒、错误率小于 0.1% 且月度预算固定的约束下，本次交互式查询场景选择 Engine B；该结论只适用于 &lt;code&gt;sql-engine-1tb-v1&lt;/code&gt; 定义的负载和环境。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这才是一份可以被评审、复现和推翻的 Benchmark 结论。&lt;/p&gt;
&lt;h1 id="3-如何建立和治理-baseline"&gt;3. 如何建立和治理 Baseline
&lt;/h1&gt;&lt;p&gt;Baseline 不是“第一次测出来的数字”那么简单。它必须能够代表某种稳定状态，并且和当前指标处于可比条件下。&lt;/p&gt;
&lt;h2 id="31-测试-baseline-与生产-baseline"&gt;3.1 测试 Baseline 与生产 Baseline
&lt;/h2&gt;&lt;p&gt;大数据工程中常见的 Baseline 可以分成两类：&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;测试 Baseline&lt;/td&gt;
 &lt;td&gt;受控 Benchmark 中已批准版本的结果&lt;/td&gt;
 &lt;td&gt;升级、调优、代码变更和硬件变更的回归判断&lt;/td&gt;
 &lt;td&gt;随发布版本显式更新，旧版本永久留档&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;生产 Baseline&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;二者不要混用。测试环境的 50 分钟成绩不能直接成为生产告警线，因为生产还存在资源争用、数据倾斜和流量波动；生产历史值也不适合直接比较两个引擎，因为它无法控制其他变量。&lt;/p&gt;
&lt;h2 id="32-建立生产-baseline-的步骤"&gt;3.2 建立生产 Baseline 的步骤
&lt;/h2&gt;&lt;p&gt;以每日离线 Pipeline 为例，可以按下面的顺序建立生产 Baseline：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定义对象&lt;/strong&gt;：明确是哪个作业、数据集、租户、区域和版本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选择指标&lt;/strong&gt;：运行时间、输入行数、输入字节、数据到达时间、失败率、Shuffle 量等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;划分可比组&lt;/strong&gt;：按工作日/周末、小时、数据量区间或业务季节分组。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选择样本窗口&lt;/strong&gt;：例如最近 28 个正常生产日，覆盖四个完整星期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;选择统计量&lt;/strong&gt;：使用中位数、P95、四分位距等稳健统计量，而不是只用平均值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定义告警规则&lt;/strong&gt;：Baseline 描述正常状态，告警阈值还要结合容忍度和 SLO。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;版本化保存&lt;/strong&gt;：记录计算口径、生效时间、排除窗口和审批人。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例如某文件在最近 30 个工作日的到达时间为：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;指标&lt;/th&gt;
 &lt;th&gt;Baseline&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P50 到达时间&lt;/td&gt;
 &lt;td&gt;08:08&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P95 到达时间&lt;/td&gt;
 &lt;td&gt;08:25&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;业务 SLO&lt;/td&gt;
 &lt;td&gt;08:35 前到达&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以将 &lt;code&gt;08:25&lt;/code&gt; 设为预警观察点，将 &lt;code&gt;08:35&lt;/code&gt; 设为违反 SLO 的严重告警点。这里的 P95 是历史 Baseline，08:35 是业务目标，两者含义不同。&lt;/p&gt;
&lt;p&gt;数据量也应该按可比组建立基线。假设工作日通常写入 0.9 亿至 1.1 亿行，周末通常只有 0.5 亿至 0.7 亿行。如果只计算一个全月平均值，周一写入 0.65 亿行可能看似正常，实际上已经明显缺数。&lt;/p&gt;
&lt;h2 id="33-静态分组与动态-baseline"&gt;3.3 静态、分组与动态 Baseline
&lt;/h2&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;静态 Baseline&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;分组 Baseline&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;滚动 Baseline&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;模型 Baseline&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;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;动态 Baseline 不能无条件自动更新。常见保护措施包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;延迟若干天再纳入新样本，给事故确认留出时间。&lt;/li&gt;
&lt;li&gt;排除已确认故障、发布窗口、回填任务和节假日活动。&lt;/li&gt;
&lt;li&gt;限制单次更新幅度，突变必须人工审批。&lt;/li&gt;
&lt;li&gt;同时保留绝对 SLO，避免 Baseline 随性能持续恶化而不断放宽。&lt;/li&gt;
&lt;li&gt;保存历史版本，保证事后能够还原“当时为什么告警”。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="34-baseline-记录示例"&gt;3.4 Baseline 记录示例
&lt;/h2&gt;&lt;p&gt;一个 Baseline 注册表可以使用数据库表或配置文件实现，重点是让参考值和上下文绑定：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;baseline_id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ingestion-runtime-prod-v4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;object&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ods_order_ingestion&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metric&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;runtime_minutes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;statistic&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;p95&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;segment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;input_size_tb&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0.8-1.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;day_type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;weekday&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;sample_window&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-07-01/2026-07-28&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;excluded_dates&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;2026-07-09&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;effective_from&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-08-01&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;owner&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;data-platform&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;当输入量变成 3 TB、作业架构重写或资源池发生变化时，应建立新版本，而不是拿不再可比的数据强行延长旧 Baseline。&lt;/p&gt;
&lt;h1 id="4-应用实战场景"&gt;4. 应用实战场景
&lt;/h1&gt;&lt;p&gt;下面的数据均为说明方法而构造，重点是展示 Benchmark、Baseline 和决策如何衔接。&lt;/p&gt;
&lt;h2 id="41-spark-版本升级性能回归门禁"&gt;4.1 Spark 版本升级：性能回归门禁
&lt;/h2&gt;&lt;h3 id="场景"&gt;场景
&lt;/h3&gt;&lt;p&gt;团队计划从 Spark 3.4 升级到 Spark 3.5，希望升级后结果正确，并且核心作业性能回退不超过 5%。&lt;/p&gt;
&lt;h3 id="benchmark-设计"&gt;Benchmark 设计
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;固定一份脱敏后的生产数据快照。&lt;/li&gt;
&lt;li&gt;选择扫描、聚合、Join、窗口函数和数据倾斜等 20 个代表性作业。&lt;/li&gt;
&lt;li&gt;使用相同节点、资源队列、JDK、作业代码和 Spark 配置。&lt;/li&gt;
&lt;li&gt;每个版本先预热 2 次，再交替运行 5 次。&lt;/li&gt;
&lt;li&gt;先比较行数、关键聚合值和校验和，再比较耗时与资源指标。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Spark 3.4 当前批准结果是&lt;strong&gt;测试 Baseline&lt;/strong&gt;。部分结果如下：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;作业&lt;/th&gt;
 &lt;th style="text-align: right"&gt;3.4 Baseline 中位耗时&lt;/th&gt;
 &lt;th style="text-align: right"&gt;3.5 中位耗时&lt;/th&gt;
 &lt;th style="text-align: right"&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;orders_etl&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;42 分钟&lt;/td&gt;
 &lt;td style="text-align: right"&gt;43 分钟&lt;/td&gt;
 &lt;td style="text-align: right"&gt;+2.4%&lt;/td&gt;
 &lt;td&gt;通过&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;customer_join&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;24 分钟&lt;/td&gt;
 &lt;td style="text-align: right"&gt;28 分钟&lt;/td&gt;
 &lt;td style="text-align: right"&gt;+16.7%&lt;/td&gt;
 &lt;td&gt;阻断&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;session_agg&lt;/code&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;31 分钟&lt;/td&gt;
 &lt;td style="text-align: right"&gt;29 分钟&lt;/td&gt;
 &lt;td style="text-align: right"&gt;-6.5%&lt;/td&gt;
 &lt;td&gt;提升&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;customer_join&lt;/code&gt; 的输出正确，但耗时回退 16.7%，同时 Shuffle Read 从 310 GB 增至 470 GB。发布门禁应阻断升级，并继续检查执行计划、Join 策略、数据倾斜和 Adaptive Query Execution 配置，而不是用另外两个作业的提升抵消它。&lt;/p&gt;
&lt;p&gt;回归判断的基本形式可以写成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;耗时回退率 = (当前耗时 - Baseline 耗时) / Baseline 耗时
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;当正确性校验失败：直接阻断
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;当耗时回退率 &amp;gt; 5%，且差异超过历史正常波动：阻断并分析
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;升级通过并稳定发布后，Spark 3.5 的批准结果可以成为下一轮变更的测试 Baseline，但 Spark 3.4 的记录仍需保留。&lt;/p&gt;
&lt;h2 id="42-查询引擎选型不仅比较谁最快"&gt;4.2 查询引擎选型：不仅比较谁最快
&lt;/h2&gt;&lt;h3 id="场景-1"&gt;场景
&lt;/h3&gt;&lt;p&gt;为 3 TB 数据上的交互式 BI 查询选择引擎。业务要求 30 并发下 P95 小于 10 秒，数据新鲜度小于 15 分钟，同时控制总成本。&lt;/p&gt;
&lt;h3 id="benchmark-设计-1"&gt;Benchmark 设计
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;从生产查询日志中抽取 120 条查询，按频率和重要性加权。&lt;/li&gt;
&lt;li&gt;使用相同数据快照、并发模型和测试时段。&lt;/li&gt;
&lt;li&gt;允许每个引擎进行合理的生产级调优，但完整披露索引、物化视图和缓存策略。&lt;/li&gt;
&lt;li&gt;成本包括计算、存储、数据导入和持续运维，不只比较机器单价。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一组示例结果如下：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;方案&lt;/th&gt;
 &lt;th style="text-align: right"&gt;P95 延迟&lt;/th&gt;
 &lt;th style="text-align: right"&gt;吞吐量&lt;/th&gt;
 &lt;th style="text-align: right"&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;Spark SQL&lt;/td&gt;
 &lt;td style="text-align: right"&gt;24.8 秒&lt;/td&gt;
 &lt;td style="text-align: right"&gt;7.2 QPS&lt;/td&gt;
 &lt;td style="text-align: right"&gt;100%&lt;/td&gt;
 &lt;td&gt;无&lt;/td&gt;
 &lt;td&gt;低&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Trino&lt;/td&gt;
 &lt;td style="text-align: right"&gt;8.6 秒&lt;/td&gt;
 &lt;td style="text-align: right"&gt;18.4 QPS&lt;/td&gt;
 &lt;td style="text-align: right"&gt;99.9%&lt;/td&gt;
 &lt;td&gt;无&lt;/td&gt;
 &lt;td&gt;中&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ClickHouse&lt;/td&gt;
 &lt;td style="text-align: right"&gt;2.1 秒&lt;/td&gt;
 &lt;td style="text-align: right"&gt;56.0 QPS&lt;/td&gt;
 &lt;td style="text-align: right"&gt;100%&lt;/td&gt;
 &lt;td&gt;需要持续导入&lt;/td&gt;
 &lt;td&gt;高&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Benchmark 不能自动替团队做决定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spark SQL 没有达到交互式查询的 P95 目标。&lt;/li&gt;
&lt;li&gt;ClickHouse 的查询性能最好，但增加了数据复制、新鲜度和运维成本。&lt;/li&gt;
&lt;li&gt;Trino 达到当前 SLO，且可以直接查询现有数据湖。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果当前目标是以较小架构改动满足 10 秒 SLO，可以选择 Trino；如果未来业务明确要求亚秒级查询，再评估 ClickHouse 的额外成本。这一结论只适用于本次负载，不能泛化为某个引擎“永远更快”。&lt;/p&gt;
&lt;h2 id="43-容量规划找到满足-sla-的安全容量"&gt;4.3 容量规划：找到满足 SLA 的安全容量
&lt;/h2&gt;&lt;h3 id="场景-2"&gt;场景
&lt;/h3&gt;&lt;p&gt;某 Ingestion Pipeline 当前每日处理 1 TB 数据，要求在 120 分钟内完成。团队需要判断现有 10 个节点能否支撑半年后的增长。&lt;/p&gt;
&lt;h3 id="benchmark-设计与结果"&gt;Benchmark 设计与结果
&lt;/h3&gt;&lt;p&gt;保持数据分布、文件格式、节点数和作业配置不变，逐级增加输入量：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: right"&gt;输入量&lt;/th&gt;
 &lt;th style="text-align: right"&gt;完成时间&lt;/th&gt;
 &lt;th&gt;是否满足 120 分钟 SLA&lt;/th&gt;
 &lt;th&gt;现象&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: right"&gt;0.5 TB&lt;/td&gt;
 &lt;td style="text-align: right"&gt;28 分钟&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;td&gt;资源充足&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: right"&gt;1.0 TB&lt;/td&gt;
 &lt;td style="text-align: right"&gt;55 分钟&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;td&gt;接近线性增长&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: right"&gt;1.5 TB&lt;/td&gt;
 &lt;td style="text-align: right"&gt;88 分钟&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;td&gt;出现少量磁盘 Spill&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: right"&gt;2.0 TB&lt;/td&gt;
 &lt;td style="text-align: right"&gt;134 分钟&lt;/td&gt;
 &lt;td&gt;否&lt;/td&gt;
 &lt;td&gt;Shuffle Spill 和长尾 Task 明显增加&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;测试说明性能在 1.5 TB 到 2 TB 之间出现拐点，不能按 0.5 TB 的结果做线性外推。下一步应在边界附近补测 1.6、1.7、1.8 TB，定位满足 SLA 的最大容量。&lt;/p&gt;
&lt;p&gt;即使最终测得 1.7 TB 可以在 120 分钟内完成，也不应把 1.7 TB 当作日常规划容量。按 20% 的资源余量计算，安全规划容量约为 1.36 TB，用于吸收重试、资源争用和数据倾斜。若半年预测达到 1.5 TB，就应提前扩容或优化分区与 Shuffle，而不是等 SLA 已经失败再处理。&lt;/p&gt;
&lt;h2 id="44-生产监控从测试成绩到动态基线"&gt;4.4 生产监控：从测试成绩到动态基线
&lt;/h2&gt;&lt;h3 id="场景-3"&gt;场景
&lt;/h3&gt;&lt;p&gt;新 Ingestion Pipeline 上线前，固定 1 TB 数据的 Benchmark 结果为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旧方案：90 分钟&lt;/li&gt;
&lt;li&gt;新方案：55 分钟&lt;/li&gt;
&lt;li&gt;上线目标：60 分钟以内&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;55 分钟&lt;/code&gt; 是一次 &lt;strong&gt;Benchmark 结果&lt;/strong&gt;。团队确认测试可重复后，可以暂时把它登记为&lt;strong&gt;初始测试 Baseline&lt;/strong&gt;，但它还不是生产系统的正常范围。&lt;/p&gt;
&lt;p&gt;上线并稳定运行 30 天后，按 0.8 至 1.2 TB 的工作日任务统计：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;指标&lt;/th&gt;
 &lt;th&gt;生产 Baseline&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;运行时间 P50&lt;/td&gt;
 &lt;td&gt;52 分钟&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;运行时间 P95&lt;/td&gt;
 &lt;td&gt;60 分钟&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;输入量正常范围&lt;/td&gt;
 &lt;td&gt;0.8 至 1.2 TB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;输入行数正常范围&lt;/td&gt;
 &lt;td&gt;0.9 亿至 1.1 亿&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;数据到达时间 P95&lt;/td&gt;
 &lt;td&gt;08:25&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这组分布才是用于日常监控的&lt;strong&gt;生产 Baseline&lt;/strong&gt;。之后某天输入量为 1 TB，作业却运行了 85 分钟，即使最终成功，也应该判定为性能异常。如果作业只运行 20 分钟，但输入行数只有 0.2 亿，则更可能是上游缺数，不能把“更快”误判为优化。&lt;/p&gt;
&lt;p&gt;这一场景中可以同时建立三类规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;数据到达告警&lt;/strong&gt;：超过历史 P95 时预警，超过业务 SLO 时严重告警。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据质量告警&lt;/strong&gt;：行数、空值率、重复率偏离同类日期的正常范围。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能告警&lt;/strong&gt;：在相近输入量下，运行时间或单位数据成本显著偏离 Baseline。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，完整链路是：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 A["上线前 Benchmark"] --&gt; B["批准测试 Baseline"]
 B --&gt; C["方案上线"]
 C --&gt; D["积累稳定生产样本"]
 D --&gt; E["建立生产 Baseline"]
 E --&gt; F["监控、告警与容量趋势"]
 F --&gt; G["变更后再次 Benchmark"]&lt;/pre&gt;&lt;h1 id="5-常见误区"&gt;5. 常见误区
&lt;/h1&gt;&lt;h2 id="51-把-benchmark-等同于压测"&gt;5.1 把 Benchmark 等同于压测
&lt;/h2&gt;&lt;p&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Benchmark&lt;/td&gt;
 &lt;td&gt;在明确、可重复的条件下，系统表现如何，方案之间如何比较？&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Load Test&lt;/td&gt;
 &lt;td&gt;在预期业务负载下，系统是否满足要求？&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Stress Test&lt;/td&gt;
 &lt;td&gt;不断增加压力时，系统在哪里失效，能否优雅降级？&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Soak Test&lt;/td&gt;
 &lt;td&gt;长时间运行后，是否出现泄漏、累积延迟或稳定性问题？&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一次 Load Test 可以被设计成可重复的 Benchmark，但二者并非天然等价。&lt;/p&gt;
&lt;h2 id="52-只看平均值"&gt;5.2 只看平均值
&lt;/h2&gt;&lt;p&gt;平均 2 秒可能掩盖 P99 为 40 秒，也可能掩盖 5% 请求直接失败。查询服务要关注延迟分位数、吞吐量和错误率；批处理要关注中位耗时、长尾 Task、失败重试和资源峰值。&lt;/p&gt;
&lt;h2 id="53-忽略正确性和工作量"&gt;5.3 忽略正确性和工作量
&lt;/h2&gt;&lt;p&gt;少扫描分区、漏掉 Join 结果或输入数据不足都会让作业“变快”。性能指标必须和输出校验、输入量、扫描字节数一起看。&lt;/p&gt;
&lt;h2 id="54-在不可比的条件下比较"&gt;5.4 在不可比的条件下比较
&lt;/h2&gt;&lt;p&gt;不同数据规模、资源配置、缓存状态、并发数和测试时段得到的结果不能直接放在一起排名。如果条件无法完全一致，应披露差异并说明它会怎样影响结论。&lt;/p&gt;
&lt;h2 id="55-发现回退后立即刷新-baseline"&gt;5.5 发现回退后立即刷新 Baseline
&lt;/h2&gt;&lt;p&gt;Baseline 的作用就是暴露变化。为了让告警消失而覆盖它，会把异常重新定义为正常。只有当变化符合预期、完成验证并经过审批后，才能创建新的 Baseline 版本。&lt;/p&gt;
&lt;h2 id="56-把外部成绩当成自己的生产承诺"&gt;5.6 把外部成绩当成自己的生产承诺
&lt;/h2&gt;&lt;p&gt;厂商或社区成绩可以帮助筛选候选方案，但无法替代自身工作负载测试。硬件、数据分布、查询模式、调优程度和成本口径不同，结论就可能完全不同。&lt;/p&gt;
&lt;h1 id="6-一份可直接复用的检查清单"&gt;6. 一份可直接复用的检查清单
&lt;/h1&gt;&lt;p&gt;执行 Benchmark 前，至少回答下面的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这次测试要支持哪个具体决策？&lt;/li&gt;
&lt;li&gt;唯一计划改变的变量是什么？&lt;/li&gt;
&lt;li&gt;数据和负载是否代表真实场景？&lt;/li&gt;
&lt;li&gt;哪些环境、配置和版本必须固定并记录？&lt;/li&gt;
&lt;li&gt;如何校验结果正确性？&lt;/li&gt;
&lt;li&gt;需要测量哪些延迟、吞吐、稳定性、资源和成本指标？&lt;/li&gt;
&lt;li&gt;预热、重复次数和执行顺序是什么？&lt;/li&gt;
&lt;li&gt;要与哪个 Baseline、Target 或 SLO 比较？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;建立 Baseline 时，再确认下面的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它是测试 Baseline 还是生产 Baseline？&lt;/li&gt;
&lt;li&gt;指标值对应什么对象、分组、数据规模和统计口径？&lt;/li&gt;
&lt;li&gt;样本窗口是否足够，是否包含事故或特殊活动？&lt;/li&gt;
&lt;li&gt;告警阈值与业务 SLO 是否单独定义？&lt;/li&gt;
&lt;li&gt;什么情况下允许更新，谁负责审批？&lt;/li&gt;
&lt;li&gt;是否保留旧版本和原始测量数据？&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="7-总结"&gt;7. 总结
&lt;/h1&gt;&lt;p&gt;最后可以用三句话记住这两个概念：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Benchmark 定义怎样测&lt;/strong&gt;：固定数据、负载、环境、步骤和指标，得到可重复的测量结果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Baseline 定义和谁比&lt;/strong&gt;：保存某个已知状态及其上下文，用来判断改善、回退或异常。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Target/SLO 定义要达到哪里&lt;/strong&gt;：它是期望或承诺，不等于当前 Baseline。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Benchmark 的结果可以被批准为测试 Baseline，稳定生产历史也可以形成生产 Baseline。真正完整的工程闭环不是“跑一次分”，而是持续执行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;明确决策 -&amp;gt; 设计 Benchmark -&amp;gt; 校验并测量 -&amp;gt; 建立 Baseline
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -&amp;gt; 监控与回归 -&amp;gt; 审批新 Baseline -&amp;gt; 保留历史
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;当测试协议可复现、参考值有上下文、判断标准提前定义时，Benchmark 和 Baseline 才能真正服务于技术选型、版本升级、容量规划和生产治理。&lt;/p&gt;
&lt;h1 id="参考资料"&gt;参考资料
&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.tpc.org/" target="_blank" rel="noopener"
 &gt;Transaction Processing Performance Council&lt;/a&gt;：TPC 系列 Benchmark 的规范与结果。&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/Intel-bigdata/HiBench" target="_blank" rel="noopener"
 &gt;Intel HiBench&lt;/a&gt;：面向大数据框架的工作负载套件。&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/brianfrankcooper/YCSB" target="_blank" rel="noopener"
 &gt;Yahoo Cloud Serving Benchmark&lt;/a&gt;：面向云数据库和 NoSQL 系统的工作负载生成与评测框架。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>【工作杂谈_20260817】多维分析与数据建模: 维度上卷——从明细粒度到汇总层级</title><link>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/multidimensionalanalysis/20260817_%E5%A4%9A%E7%BB%B4%E5%88%86%E6%9E%90%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%BB%BA%E6%A8%A1_%E7%BB%B4%E5%BA%A6%E4%B8%8A%E5%8D%B7/</link><pubDate>Mon, 17 Aug 2026 20:51:34 +0800</pubDate><guid>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/multidimensionalanalysis/20260817_%E5%A4%9A%E7%BB%B4%E5%88%86%E6%9E%90%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%BB%BA%E6%A8%A1_%E7%BB%B4%E5%BA%A6%E4%B8%8A%E5%8D%B7/</guid><description>&lt;h1 id="1-什么是维度上卷"&gt;1. 什么是“维度上卷”
&lt;/h1&gt;&lt;p&gt;维度上卷 Roll-up 本质上是维度建模和 OLAP 中的分析概念；ETL 中的维度上卷，是通过数据转换和聚合，将细粒度事实数据加工为更高层级、更粗粒度的汇总数据。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Roll-up 是分析语义&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ETL 是实现手段&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;聚合表、汇总事实表是落地结果&lt;/strong&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;
&lt;p&gt;时间维度：日 → 月 → 季度 → 年&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;地域维度：城市 → 省份 → 国家&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;产品维度：SKU → SubCategory → Category&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如原始数据是：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Date&lt;/th&gt;
 &lt;th&gt;City&lt;/th&gt;
 &lt;th&gt;Province&lt;/th&gt;
 &lt;th&gt;Country&lt;/th&gt;
 &lt;th&gt;Product&lt;/th&gt;
 &lt;th&gt;Revenue&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;City1&lt;/td&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;Country1&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;100&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-02&lt;/td&gt;
 &lt;td&gt;City3&lt;/td&gt;
 &lt;td&gt;Province2&lt;/td&gt;
 &lt;td&gt;Country1&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;150&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;City2&lt;/td&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;Country1&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;80&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;经过时间和地域上卷（C1/C2都属于P1）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Month&lt;/th&gt;
 &lt;th&gt;Province&lt;/th&gt;
 &lt;th&gt;Product&lt;/th&gt;
 &lt;th&gt;Revenue&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;180&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;Province12&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;150&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;code&gt;Date → Month&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;City → Province&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;同时：&lt;code&gt;Revenue = SUM(100 + 150 + 80) = 330&lt;/code&gt;&lt;/p&gt;
&lt;h1 id="2-etl-中上卷的核心是改变事实粒度"&gt;2. ETL 中上卷的核心是“改变事实粒度”
&lt;/h1&gt;&lt;p&gt;上卷不能只理解为 &lt;code&gt;GROUP BY&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它真正改变的是：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;事实表中一行数据所代表的业务范围。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&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;某一天 + 某城市 + 某SKU 的销售情况&lt;/td&gt;
 &lt;td&gt;Date + City + SKU&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;上卷后&lt;/td&gt;
 &lt;td&gt;某个月 + 某省份 + 某产品类别的销售情况&lt;/td&gt;
 &lt;td&gt;Month + Province + Category&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;p&gt;即 日级 → 月级 通常是可完成的；&lt;/p&gt;
&lt;p&gt;但 月级 → 日级 如果没有保留日级明细，无法准确恢复。&lt;/p&gt;
&lt;h1 id="3-etl-中上卷的完整流程"&gt;3. ETL 中上卷的完整流程
&lt;/h1&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 A["原始数据"] --&gt; B["Step1: 数据质量检查"]

 B --&gt; B1["检查 City → Province 是否唯一"]
 B --&gt; B2["检查 Province → Country 是否唯一"]
 B --&gt; B3["识别缺失、错误和历史变更"]

 B1 --&gt; C["Step2: 加载维表"]
 B2 --&gt; C
 B3 --&gt; C

 C --&gt; C1["DimDate"]
 C --&gt; C2["DimGeography"]
 C --&gt; C3["DimProduct"]

 C1 --&gt; D["Step3: 生成明细事实表"]
 C2 --&gt; D
 C3 --&gt; D

 D --&gt; D1["FactRevenueDaily"]
 D1 --&gt; E["Step4: 预聚合上卷"]

 E --&gt; E1["FactRevenueMonthlyCity"]
 E --&gt; E2["FactRevenueMonthlyProvince"]
 E --&gt; E3["FactRevenueMonthlyCountry"]

 E1 --&gt; F["Step5: 一致性校验"]
 E2 --&gt; F
 E3 --&gt; F

 F --&gt; F1["各级 Revenue 总和均应为 330"]

 classDef step fill:#FFE08A,stroke:#A86700,color:#3D2900,stroke-width:2px
 class B,C,D,E,F step&lt;/pre&gt;&lt;h2 id="31-数据质量检查"&gt;3.1 数据质量检查
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;检查 City → Province&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;City&lt;/th&gt;
 &lt;th&gt;Province&lt;/th&gt;
 &lt;th&gt;是否唯一&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;City1&lt;/td&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;City2&lt;/td&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;City3&lt;/td&gt;
 &lt;td&gt;Province2&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;当前数据中，每个 City 只属于一个 Province，因此层级关系正常。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查 Province → Country&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Province&lt;/th&gt;
 &lt;th&gt;Country&lt;/th&gt;
 &lt;th&gt;是否唯一&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;Country1&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Province2&lt;/td&gt;
 &lt;td&gt;Country1&lt;/td&gt;
 &lt;td&gt;是&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;最终可以建立稳定的地理层级：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 A1["City1"] --&gt; B1["Province1"]
 A2["City2"] --&gt; B1["Province1"]
 A3["City3"] --&gt; B2["Province2"]

 B1["Province1"] --&gt; C2["Country1"]
 B2["Province2"] --&gt; C2["Country1"]&lt;/pre&gt;&lt;h2 id="32-加载维表"&gt;3.2 加载维表
&lt;/h2&gt;&lt;p&gt;不建议在事实表里重复保存 City、Province、Country 名称。所以按照星型模型可以&lt;strong&gt;拆分事实数据与维度数据&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;地域维表 &lt;code&gt;DimGeography&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;日期维表 &lt;code&gt;DimDate&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;产品表 &lt;code&gt;DimProduct&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="地域维表-dimgeography"&gt;地域维表 DimGeography
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;GeographyKey&lt;/th&gt;
 &lt;th&gt;City&lt;/th&gt;
 &lt;th&gt;Province&lt;/th&gt;
 &lt;th&gt;Country&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;101&lt;/td&gt;
 &lt;td&gt;City1&lt;/td&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;Country1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;102&lt;/td&gt;
 &lt;td&gt;City3&lt;/td&gt;
 &lt;td&gt;Province2&lt;/td&gt;
 &lt;td&gt;Country2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;103&lt;/td&gt;
 &lt;td&gt;City2&lt;/td&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;Country1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="日期维表-dimdate"&gt;日期维表 DimDate
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;DateKey&lt;/th&gt;
 &lt;th&gt;Date&lt;/th&gt;
 &lt;th&gt;Month&lt;/th&gt;
 &lt;th&gt;Quarter&lt;/th&gt;
 &lt;th&gt;Year&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;20260801&lt;/td&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;2026-Q3&lt;/td&gt;
 &lt;td&gt;2026&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;20260802&lt;/td&gt;
 &lt;td&gt;2026-08-02&lt;/td&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;2026-Q3&lt;/td&gt;
 &lt;td&gt;2026&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="产品表-dimproduct"&gt;产品表 DimProduct
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;ProductKey&lt;/th&gt;
 &lt;th&gt;Product&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;201&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果存在产品类别，还可以设计为：Product → ProductCategory → ProductLine&lt;/p&gt;
&lt;h2 id="33-生成明细事实表"&gt;3.3 生成明细事实表
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;明细事实表&lt;code&gt;FactRevenueDaily&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;DateKey&lt;/th&gt;
 &lt;th&gt;GeographyKey&lt;/th&gt;
 &lt;th&gt;ProductKey&lt;/th&gt;
 &lt;th&gt;Revenue&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;20260801&lt;/td&gt;
 &lt;td&gt;101&lt;/td&gt;
 &lt;td&gt;201&lt;/td&gt;
 &lt;td&gt;100&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;20260802&lt;/td&gt;
 &lt;td&gt;103&lt;/td&gt;
 &lt;td&gt;201&lt;/td&gt;
 &lt;td&gt;150&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;20260801&lt;/td&gt;
 &lt;td&gt;102&lt;/td&gt;
 &lt;td&gt;201&lt;/td&gt;
 &lt;td&gt;80&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;粒度仍然是：&lt;code&gt;Date + GeographyKey + Product&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;City、Province、Country 名称不需要重复保存在事实表中，而是通过 &lt;code&gt;GeographyKey&lt;/code&gt; 关联 &lt;code&gt;DimGeography&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="34-预聚合上卷"&gt;3.4 预聚合上卷
&lt;/h2&gt;&lt;h3 id="上卷一date--month"&gt;上卷一：Date → Month
&lt;/h3&gt;&lt;p&gt;目标粒度：Month + City + Product，生成 &lt;code&gt;FactRevenueMonthlyCity&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Month&lt;/th&gt;
 &lt;th&gt;City&lt;/th&gt;
 &lt;th&gt;Product&lt;/th&gt;
 &lt;th&gt;Revenue&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;City1&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;100&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;City2&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;80&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;City3&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;150&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这里只提升了时间层级：Date → Month&lt;/p&gt;
&lt;p&gt;City 维度仍保留在原层级&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="上卷二city--province"&gt;上卷二：City → Province
&lt;/h3&gt;&lt;p&gt;目标粒度：Month + Province + Product，生成 &lt;code&gt;FactRevenueMonthlyProvince&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Month&lt;/th&gt;
 &lt;th&gt;Province&lt;/th&gt;
 &lt;th&gt;Product&lt;/th&gt;
 &lt;th&gt;Revenue&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;Province1&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;180&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;Province2&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;150&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;计算过程：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Province1 = City1 100 + City2 80 = 180
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Province2 = City3 150
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里同时发生：&lt;/p&gt;
&lt;p&gt;Date → Month&lt;/p&gt;
&lt;p&gt;City → Province&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="上卷三province--country"&gt;上卷三：Province → Country
&lt;/h3&gt;&lt;p&gt;目标粒度：Month + Country + Product，生成 &lt;code&gt;FactRevenueMonthlyCountry&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Month&lt;/th&gt;
 &lt;th&gt;Country&lt;/th&gt;
 &lt;th&gt;Product&lt;/th&gt;
 &lt;th&gt;Revenue&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08&lt;/td&gt;
 &lt;td&gt;Country1&lt;/td&gt;
 &lt;td&gt;A&lt;/td&gt;
 &lt;td&gt;330&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;计算过程：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Country1 = Province1 180 + Province2 150 = 330
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;完整上卷路径为：&lt;/p&gt;
&lt;p&gt;Date → Month&lt;/p&gt;
&lt;p&gt;City → Province → Country&lt;/p&gt;
&lt;h2 id="35-一致性校验"&gt;3.5 一致性校验
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;层级&lt;/th&gt;
 &lt;th&gt;Revenue 总和&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;原始明细数据&lt;/td&gt;
 &lt;td&gt;330&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Monthly City&lt;/td&gt;
 &lt;td&gt;330&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Monthly Province&lt;/td&gt;
 &lt;td&gt;330&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Monthly Country&lt;/td&gt;
 &lt;td&gt;330&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因此满足：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;明细层 Revenue
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;= 城市层 Revenue
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;= 省份层 Revenue
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;= 国家层 Revenue
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;= 330
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;最终的数据流可以概括为：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 A["明细事实:Date + City + Product"] --&gt; B["月度城市汇总:Month + City + Product"]

 B["月度城市汇总:Month + City + Product"] --&gt; C["月度省份汇总:Month + Province + Product"]

 C["月度省份汇总:Month + Province + Product"] --&gt; D["月度国家汇总:Month + Country + Product"]&lt;/pre&gt;&lt;p&gt;这就是 ETL 中维度上卷的核心：&lt;strong&gt;维度层级向上提升，事实粒度变粗，同时按照目标粒度重新聚合度量值。&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>【工作杂谈_20260815】用户分析与身份体系: CanonicalUserId：从用户标识到身份图谱</title><link>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/useranalyticsidentity/20260815_%E7%94%A8%E6%88%B7%E5%88%86%E6%9E%90%E4%B8%8E%E8%BA%AB%E4%BB%BD%E4%BD%93%E7%B3%BB_canonicaluserid%E8%BA%AB%E4%BB%BD%E8%A7%A3%E6%9E%90/</link><pubDate>Sat, 15 Aug 2026 22:15:38 +0800</pubDate><guid>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/useranalyticsidentity/20260815_%E7%94%A8%E6%88%B7%E5%88%86%E6%9E%90%E4%B8%8E%E8%BA%AB%E4%BB%BD%E4%BD%93%E7%B3%BB_canonicaluserid%E8%BA%AB%E4%BB%BD%E8%A7%A3%E6%9E%90/</guid><description>&lt;h1 id="1-先说结论canonicaluserid-到底是什么"&gt;1. 先说结论：CanonicalUserId 到底是什么
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;CanonicalUserId 不是行业统一标准字段，也不是天然存在于客户端的某个 ID。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;更准确地说，它是身份解析系统根据一组用户标识及其关联证据，为某个业务实体生成或选定的&lt;strong&gt;内部主键&lt;/strong&gt;。它让下游不必分别使用 &lt;code&gt;user_id&lt;/code&gt;、&lt;code&gt;device_id&lt;/code&gt;、Cookie ID、手机号或第三方平台 ID，而是通过一个统一键查询该实体的事件、属性和关系。&lt;/p&gt;
&lt;p&gt;可以先记住下面这句话：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;CanonicalUserId 是身份解析的结果，不是身份解析的依据；它代表的是规则定义下的业务实体，不一定等于现实世界中的自然人。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;例如，系统收集到以下标识：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;anonymous_id = web_7f8a
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;device_id = ios_a91c
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;account_id = user_1001
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;crm_id = crm_8872
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;经过可靠的登录、绑定或业务系统映射后，它们可能被归到：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;canonical_user_id = cu_01J5YQ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;cu_01J5YQ...&lt;/code&gt; 通常只是一个不透明、稳定的内部代理键。它本身不包含手机号、邮箱等业务含义。&lt;/p&gt;
&lt;h2 id="11-第一个问题不是用哪个-id而是要统一什么实体"&gt;1.1 第一个问题不是“用哪个 ID”，而是“要统一什么实体”
&lt;/h2&gt;&lt;p&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;自然人 Person&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;账号 Account&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;设备 Device&lt;/td&gt;
 &lt;td&gt;一次安装或浏览器实例&lt;/td&gt;
 &lt;td&gt;设备风控、推送、匿名分析&lt;/td&gt;
 &lt;td&gt;会重装、清 Cookie，也可能多人共用&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;会话 Session&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;家庭 Household&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;企业/组织 Organization&lt;/td&gt;
 &lt;td&gt;公司、门店或租户&lt;/td&gt;
 &lt;td&gt;B2B SaaS、账号层分析&lt;/td&gt;
 &lt;td&gt;一个自然人可属于多个组织&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因此，在建立 CanonicalUserId 之前必须先写清楚：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;这个 ID 代表自然人、账号，还是设备？&lt;/li&gt;
&lt;li&gt;是否允许一个人对应多个账号？&lt;/li&gt;
&lt;li&gt;是否允许一个账号被多人共用？&lt;/li&gt;
&lt;li&gt;发生账号合并、手机号换绑时，历史如何处理？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于大多数产品行为分析，&lt;strong&gt;把“稳定业务账号”作为已登录用户实体，把匿名浏览器或 App 安装实例作为临时实体&lt;/strong&gt;，往往比宣称“已经识别出自然人”更准确。&lt;/p&gt;
&lt;h2 id="12-几个容易混用的概念"&gt;1.2 几个容易混用的概念
&lt;/h2&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;Identifier&lt;/td&gt;
 &lt;td&gt;外部系统产生的标识值&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;user_1001&lt;/code&gt;、&lt;code&gt;web_7f8a&lt;/code&gt;、手机号&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Namespace&lt;/td&gt;
 &lt;td&gt;标识所属的命名空间和语义&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;account_id&lt;/code&gt;、&lt;code&gt;anonymous_id&lt;/code&gt;、&lt;code&gt;crm_id&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Identity Edge&lt;/td&gt;
 &lt;td&gt;两个标识属于同一实体的证据&lt;/td&gt;
 &lt;td&gt;某设备成功登录了某账号&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Identity Graph&lt;/td&gt;
 &lt;td&gt;以标识为节点、关联证据为边的图&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;web_7f8a -&amp;gt; user_1001 &amp;lt;- ios_a91c&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Identity Resolution&lt;/td&gt;
 &lt;td&gt;根据规则合并、拒绝或拆分身份的过程&lt;/td&gt;
 &lt;td&gt;登录绑定、冲突检测、解绑&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CanonicalUserId&lt;/td&gt;
 &lt;td&gt;解析后实体的稳定内部主键&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;cu_01J5YQ...&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;User Profile&lt;/td&gt;
 &lt;td&gt;以统一主键聚合出的属性和行为视图&lt;/td&gt;
 &lt;td&gt;会员等级、最近活跃时间、累计消费&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;One ID&lt;/code&gt;、&lt;code&gt;ID-Mapping&lt;/code&gt;、&lt;code&gt;Identity Graph&lt;/code&gt;、&lt;code&gt;Entity Resolution&lt;/code&gt; 经常在同一个话题中出现，但侧重点不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ID-Mapping&lt;/strong&gt; 强调标识之间如何建立映射。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identity Graph&lt;/strong&gt; 强调标识和实体之间的图关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entity Resolution&lt;/strong&gt; 范围更广，还可能根据姓名、地址、邮箱等进行规则或概率匹配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CanonicalUserId / One ID&lt;/strong&gt; 强调最终供下游使用的统一主键。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="2-为什么需要身份归一"&gt;2. 为什么需要身份归一
&lt;/h1&gt;&lt;h2 id="21-一个用户为什么会产生多个标识"&gt;2.1 一个用户为什么会产生多个标识
&lt;/h2&gt;&lt;p&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;T1&lt;/td&gt;
 &lt;td&gt;在浏览器匿名查看商品&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;anonymous_id=web_7f8a&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T2&lt;/td&gt;
 &lt;td&gt;在该浏览器注册并登录&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;web_7f8a + account_id=user_1001&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T3&lt;/td&gt;
 &lt;td&gt;在 iPhone App 匿名浏览&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;device_id=ios_a91c&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T4&lt;/td&gt;
 &lt;td&gt;在 App 登录同一账号&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ios_a91c + account_id=user_1001&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;T5&lt;/td&gt;
 &lt;td&gt;在线下 CRM 完善资料&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;account_id=user_1001 + crm_id=crm_8872&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果只按原始标识计数，系统可能把它看成四个用户；如果利用 T2、T4、T5 的确定性关联，则可以得到：&lt;/p&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 W["anonymous_id:web_7f8a"] --&gt;|成功登录| A["account_id:user_1001"]
 D["device_id:ios_a91c"] --&gt;|成功登录| A
 C["crm_id:crm_8872"] --&gt;|业务主数据映射| A
 A --&gt; P["canonical_user_id:cu_01J5YQ"]&lt;/pre&gt;&lt;p&gt;身份归一真正解决的是：&lt;strong&gt;同一个业务实体在不同时间、终端和系统中留下了不同的键，而分析与服务需要一个连续视图。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="22-不做身份归一会怎样"&gt;2.2 不做身份归一会怎样
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;匿名浏览和注册事件分属两人，注册漏斗被截断。&lt;/li&gt;
&lt;li&gt;同一账号换设备后被重复计算，AU、留存和新增用户数偏高。&lt;/li&gt;
&lt;li&gt;广告点击、站内浏览和最终订单无法形成完整归因链路。&lt;/li&gt;
&lt;li&gt;客服、CRM、交易和行为数据无法拼成统一客户视图。&lt;/li&gt;
&lt;li&gt;已退订用户在另一个标识下仍被营销触达。&lt;/li&gt;
&lt;li&gt;风控系统看不到同一账号与多个设备、支付工具之间的关系。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不过，错误合并通常比漏合并更危险。漏合并会把一人看成多人；错误合并则可能把甲的画像、订单或营销授权给乙，既污染数据，也可能形成隐私和合规事故。&lt;/p&gt;
&lt;h2 id="23-为什么-user_id--device_id-不能成为统一用户键"&gt;2.3 为什么 &lt;code&gt;user_id + device_id&lt;/code&gt; 不能成为统一用户键
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;user_id + device_id&lt;/code&gt; 描述的是“某账号在某设备上的组合”，不是用户实体：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个账号登录三台设备，会产生三个组合键。&lt;/li&gt;
&lt;li&gt;同一设备先后登录两个账号，会产生两个组合键。&lt;/li&gt;
&lt;li&gt;匿名阶段没有 &lt;code&gt;user_id&lt;/code&gt;，组合键无法生成。&lt;/li&gt;
&lt;li&gt;清 Cookie、重装 App 或更换设备后，组合键会再次变化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;同样，下面这种写法也只能作为临时降级方案：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;COALESCE(user_id, device_id)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;匿名时它返回 &lt;code&gt;device_id&lt;/code&gt;，登录后却返回 &lt;code&gt;user_id&lt;/code&gt;，前后仍是两个用户。只有在身份解析已经建立 &lt;code&gt;device_id -&amp;gt; canonical_user_id&lt;/code&gt; 映射后，&lt;code&gt;COALESCE&lt;/code&gt; 才能用于选择解析结果或兜底标识。&lt;/p&gt;
&lt;h1 id="3-身份归一是怎样工作的"&gt;3. 身份归一是怎样工作的
&lt;/h1&gt;&lt;h2 id="31-把标识看成节点把证据看成边"&gt;3.1 把标识看成节点，把证据看成边
&lt;/h2&gt;&lt;p&gt;最直观的模型是图：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;节点：&lt;code&gt;(namespace, normalized_value)&lt;/code&gt;，例如 &lt;code&gt;(account_id, user_1001)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;边：两个节点属于同一实体的证据，例如“设备 D 在认证成功后登录账号 U”。&lt;/li&gt;
&lt;li&gt;连通实体：经过规则校验后可归并到同一 CanonicalUserId 的节点集合。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么节点必须包含 Namespace？因为不同系统都可能出现值 &lt;code&gt;12345&lt;/code&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(crm_id, 12345) != (account_id, 12345) != (store_member_id, 12345)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;一条生产可用的边也不能只有 &lt;code&gt;left_id&lt;/code&gt; 和 &lt;code&gt;right_id&lt;/code&gt;，至少还要能回答：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关系来自登录、人工绑定、CRM 导入，还是模型推断？&lt;/li&gt;
&lt;li&gt;哪个系统、哪条事件产生了这条关系？&lt;/li&gt;
&lt;li&gt;关系何时生效、何时失效？&lt;/li&gt;
&lt;li&gt;使用了哪个规则版本？&lt;/li&gt;
&lt;li&gt;能否撤销，发生冲突时如何重算？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="32-关联证据并不等价"&gt;3.2 关联证据并不等价
&lt;/h2&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;服务端确认账号登录、账号合并、主数据映射&lt;/td&gt;
 &lt;td&gt;可直接建立高可信边&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&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;/td&gt;
 &lt;td&gt;Cookie、广告 ID、安装 ID、设备 ID&lt;/td&gt;
 &lt;td&gt;可辅助串联匿名行为，不应单独证明自然人相同&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;概率证据&lt;/td&gt;
 &lt;td&gt;姓名、地址、IP、User-Agent、行为相似度&lt;/td&gt;
 &lt;td&gt;输出候选或置信度，谨慎自动合并&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;产品行为分析通常优先采用&lt;strong&gt;确定性匹配&lt;/strong&gt;：只有同一事件或可信业务关系同时带出两个标识时才建边。跨 CRM 的客户去重可能使用规则或机器学习匹配，但必须保留分数、阈值和人工复核机制。&lt;/p&gt;
&lt;p&gt;需要特别注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;邮箱和手机号会变更、共享或被运营商回收，不是天然永久主键。&lt;/li&gt;
&lt;li&gt;IP 地址、机型和 User-Agent 的共现不能证明是同一人。&lt;/li&gt;
&lt;li&gt;对邮箱或手机号做哈希只是降低明文暴露，不会让错误数据变准确，也不自动满足匿名化要求。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="33-标识规范化先于匹配"&gt;3.3 标识规范化先于匹配
&lt;/h2&gt;&lt;p&gt;身份解析前通常需要进行标准化和质量校验：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;邮箱按业务规则处理大小写、空格和非法格式。&lt;/li&gt;
&lt;li&gt;手机号转换为带国家码的 E.164 格式。&lt;/li&gt;
&lt;li&gt;明确业务 ID 是否区分大小写，不应无条件转小写。&lt;/li&gt;
&lt;li&gt;拒绝 &lt;code&gt;null&lt;/code&gt;、&lt;code&gt;unknown&lt;/code&gt;、&lt;code&gt;-1&lt;/code&gt;、全零值等公共占位符。&lt;/li&gt;
&lt;li&gt;每个来源系统声明它能生产哪些 Namespace，以及这些值是否唯一。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果把公共占位符当成有效标识，数以万计的用户可能通过 &lt;code&gt;user_id=null&lt;/code&gt; 被合并成一个巨型身份簇，这类事故常被称为 &lt;strong&gt;Graph Collapse（图坍缩）&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="34-canonicaluserid-应该怎么生成"&gt;3.4 CanonicalUserId 应该怎么生成
&lt;/h2&gt;&lt;p&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;code&gt;user_id&lt;/code&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;平台选定某个已有 ID&lt;/td&gt;
 &lt;td&gt;无需额外生成主键&lt;/td&gt;
 &lt;td&gt;选择规则可能不可控，外部 ID 变化会影响使用&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;生成不透明内部 ID&lt;/td&gt;
 &lt;td&gt;与邮箱、设备、账号解耦，便于合并和版本管理&lt;/td&gt;
 &lt;td&gt;需要额外维护成员关系和重定向表&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;复杂系统通常更适合第三种方案：为 Profile 生成 UUID、ULID 或内部序列号，把所有外部标识作为成员挂到 Profile 下。&lt;/p&gt;
&lt;p&gt;当两个 Profile 合并时，不要批量修改所有下游事实表的主键。可以保留一个存活 ID，并记录重定向：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cu_old_02 -&amp;gt; cu_survivor_01
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;下游解析时沿重定向找到当前主 ID，同时仍能追溯历史上使用过的 ID。&lt;/p&gt;
&lt;h2 id="35-为什么不能直接对所有边求连通分量"&gt;3.5 为什么不能直接对所有边求连通分量
&lt;/h2&gt;&lt;p&gt;假设一台共享平板先后登录两个账号：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;account_A -- device_shared -- account_B
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果只使用“存在路径即同一人”的传递闭包，&lt;code&gt;account_A&lt;/code&gt; 和 &lt;code&gt;account_B&lt;/code&gt; 会被合并。这显然不合理：设备可以共享，而账号 ID 在当前业务中可能要求一个 Profile 最多只能有一个。&lt;/p&gt;
&lt;p&gt;生产系统会增加约束和保护规则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;唯一性约束&lt;/strong&gt;：一个 Profile 最多允许一个 &lt;code&gt;account_id&lt;/code&gt; 或 &lt;code&gt;crm_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标识优先级&lt;/strong&gt;：稳定账号 ID 高于邮箱，邮箱高于匿名 ID。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数量与时间窗口&lt;/strong&gt;：限制一个 Profile 在一定时间内可关联的设备、邮箱或匿名 ID 数量。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;来源可信度&lt;/strong&gt;：服务端认证关系高于客户端自报关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阻断值&lt;/strong&gt;：占位符、测试账号和已知异常值不能参与合并。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;冲突隔离&lt;/strong&gt;：规则无法判断时进入待处理队列，而不是强行归并。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可解绑与重算&lt;/strong&gt;：错误边失效后可以重新计算受影响的局部身份簇。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="36-确定性-id-mapping-与概率实体解析"&gt;3.6 确定性 ID-Mapping 与概率实体解析
&lt;/h2&gt;&lt;p&gt;两者解决的是相邻但不完全相同的问题：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;维度&lt;/th&gt;
 &lt;th&gt;确定性 ID-Mapping&lt;/th&gt;
 &lt;th&gt;概率 Entity Resolution&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;输入&lt;/td&gt;
 &lt;td&gt;登录关系、绑定关系、稳定业务 ID&lt;/td&gt;
 &lt;td&gt;姓名、地址、邮箱、电话等属性&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&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;/td&gt;
 &lt;td&gt;绑定/不绑定&lt;/td&gt;
 &lt;td&gt;匹配组和置信度&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;典型场景&lt;/td&gt;
 &lt;td&gt;埋点、跨设备产品分析&lt;/td&gt;
 &lt;td&gt;多 CRM 去重、线下客户主数据整合&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;主要风险&lt;/td&gt;
 &lt;td&gt;埋点错误导致错误合并&lt;/td&gt;
 &lt;td&gt;阈值不当导致误匹配或漏匹配&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;第一次建设时，应该先把确定性的登录和绑定链路做好，再评估是否真的需要概率匹配。不要用 IP、设备指纹等弱信号偷偷“猜人”，然后把结果包装成确定的 CanonicalUserId。&lt;/p&gt;
&lt;h1 id="4-用完整用户旅程理解-canonicaluserid"&gt;4. 用完整用户旅程理解 CanonicalUserId
&lt;/h1&gt;&lt;h2 id="41-匿名浏览到登录"&gt;4.1 匿名浏览到登录
&lt;/h2&gt;&lt;p&gt;以 Mixpanel 的 &lt;strong&gt;Simplified ID Merge API&lt;/strong&gt; 为例，客户端 SDK 会先生成 &lt;code&gt;$device_id&lt;/code&gt;。调用 &lt;code&gt;identify(user_id)&lt;/code&gt; 后，后续事件会同时包含 &lt;code&gt;$device_id&lt;/code&gt; 和 &lt;code&gt;$user_id&lt;/code&gt;；当 Mixpanel 首次收到二者共存的事件时，会建立映射。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;mixpanel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;identify&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;user_1001&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;mixpanel&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;track&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Login Succeeded&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;官方文档特别要求 &lt;code&gt;identify()&lt;/code&gt; 后至少发送一个事件，因为 Simplified API 是由同时包含 &lt;code&gt;$device_id&lt;/code&gt; 与 &lt;code&gt;$user_id&lt;/code&gt; 的事件触发合并，而不是仅凭本地函数调用完成服务端映射。&lt;/p&gt;
&lt;p&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;事件发生时实际携带的匿名 ID、账号 ID、时间和来源&lt;/td&gt;
 &lt;td&gt;不重写&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;解析结果&lt;/td&gt;
 &lt;td&gt;当前或指定版本下的 CanonicalUserId&lt;/td&gt;
 &lt;td&gt;可随规则重算&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这样既能用“当前映射”回看完整注册漏斗，也能用“事件发生时映射”复现历史报表。不同平台的“回溯合并”实现并不相同：有的平台在查询时应用映射，有的平台会更新分析索引；不能简单理解为所有历史原始事件都被物理改写。&lt;/p&gt;
&lt;h2 id="42-跨设备统一"&gt;4.2 跨设备统一
&lt;/h2&gt;&lt;p&gt;跨设备不是通过相似设备特征自动完成的，而是两个设备都出现了同一个可信账号标识：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;web_7f8a --登录--&amp;gt; user_1001
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ios_a91c --登录--&amp;gt; user_1001
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;于是二者可解析到同一个 CanonicalUserId。若 Mac 浏览器始终没有登录、绑定或其他可靠证据，它就应继续保持匿名。&lt;strong&gt;无法确认的身份不合并，是正确结果，不是系统缺陷。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="43-退出登录与共享设备"&gt;4.3 退出登录与共享设备
&lt;/h2&gt;&lt;p&gt;共享设备是最容易发生错误合并的场景。建议把以下状态变化写入统一的 SDK 规范：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;登录成功后设置稳定账号 ID，并发送一条成功事件。&lt;/li&gt;
&lt;li&gt;退出或认证超时后清理本地账号身份。&lt;/li&gt;
&lt;li&gt;当使用者可能变化时，生成新的匿名 ID 或会话作用域 ID。&lt;/li&gt;
&lt;li&gt;不要把硬件设备 ID 永久等同于最近一次登录账号。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;reset()&lt;/code&gt; 的具体语义是厂商和版本相关的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mixpanel 当前 Simplified API 文档建议在退出或认证超时时调用 &lt;code&gt;.reset()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Mixpanel Original API 文档为了避免频繁增加身份簇成员，讨论了只在明确切换使用者时 reset 的策略。&lt;/li&gt;
&lt;li&gt;Amplitude 要显式匿名化时，建议把 User ID 设为 &lt;code&gt;null&lt;/code&gt;，同时生成新的 Device ID；否则后续匿名事件可能继续归到该设备上最后一个已知用户。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，不能总结成“退出一定 reset”或“普通退出永远不 reset”。正确做法是先确认所用 SDK 版本，再根据共享设备、匿名期归因和身份簇限制设计状态机。&lt;/p&gt;
&lt;h2 id="44-账号合并解绑与手机号回收"&gt;4.4 账号合并、解绑与手机号回收
&lt;/h2&gt;&lt;p&gt;真实系统还会遇到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户把两个业务账号申请合并。&lt;/li&gt;
&lt;li&gt;客服误绑账号后需要撤销。&lt;/li&gt;
&lt;li&gt;手机号从用户 A 换绑，并在未来被用户 B 使用。&lt;/li&gt;
&lt;li&gt;企业客户更换 CRM，主数据 ID 被重新编码。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这说明映射不是静态字典，而是带时间和版本的关系：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(phone, +86138...) -&amp;gt; account_A [2025-01-01, 2026-03-01)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(phone, +86138...) -&amp;gt; account_B [2026-07-01, ...)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;解绑不应删除原始事件和审计记录，而应使关系在某个时间点失效，并重新计算受影响的 Profile。对历史报表，还要明确使用“当时身份”还是“当前最新身份”。&lt;/p&gt;
&lt;h2 id="45-canonicaluserid-能支持哪些应用"&gt;4.5 CanonicalUserId 能支持哪些应用
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;应用&lt;/th&gt;
 &lt;th&gt;CanonicalUserId 的作用&lt;/th&gt;
 &lt;th&gt;仍需额外定义的规则&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;AU、留存&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;/td&gt;
 &lt;td&gt;串联注册前匿名行为与注册后行为&lt;/td&gt;
 &lt;td&gt;转化窗口、匿名行为归属策略&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;渠道归因&lt;/td&gt;
 &lt;td&gt;连接广告点击、站内行为和订单&lt;/td&gt;
 &lt;td&gt;First-touch、Last-touch 等归因模型&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;用户画像&lt;/td&gt;
 &lt;td&gt;汇总 CRM、交易和行为属性&lt;/td&gt;
 &lt;td&gt;属性优先级、有效期、冲突规则&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&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;/td&gt;
 &lt;td&gt;发现账号、设备、支付工具之间的关系&lt;/td&gt;
 &lt;td&gt;风险边、图遍历、模型和人工审核&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CanonicalUserId 只解决“这些记录当前应归到哪个实体”。它不会自动解决指标口径、归因模型、画像属性冲突和隐私授权。&lt;/p&gt;
&lt;h1 id="5-从旧方案到身份图谱"&gt;5. 从旧方案到身份图谱
&lt;/h1&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;code&gt;device_id&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;匿名设备内连续访问&lt;/td&gt;
 &lt;td&gt;换设备、清 Cookie、共享设备都会失真&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;只用 &lt;code&gt;user_id&lt;/code&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;COALESCE(user_id, device_id)&lt;/code&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;user_id + device_id&lt;/code&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;user_id -&amp;gt; device_id&lt;/code&gt; 映射&lt;/td&gt;
 &lt;td&gt;最简单的一对一关联&lt;/td&gt;
 &lt;td&gt;无法表示多设备、多个 Namespace、历史和解绑&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;节点、边、Profile 模型&lt;/td&gt;
 &lt;td&gt;多标识、可追溯、可合并和拆分&lt;/td&gt;
 &lt;td&gt;需要规则治理、版本管理和质量监控&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;并不是所有公司都必须一开始就建设图平台：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;产品只允许登录后使用，而且 &lt;code&gt;user_id&lt;/code&gt; 全局稳定时，直接使用 &lt;code&gt;user_id&lt;/code&gt; 已经足够。&lt;/li&gt;
&lt;li&gt;需要串联注册前后行为时，增加 &lt;code&gt;anonymous_id -&amp;gt; user_id&lt;/code&gt; 的确定性映射即可。&lt;/li&gt;
&lt;li&gt;需要跨 CRM、多渠道、绑定/解绑和共享设备治理时，才需要完整 Identity Graph。&lt;/li&gt;
&lt;li&gt;需要根据姓名、地址等模糊属性匹配时，再引入 Entity Resolution。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;复杂度应该由真实业务问题推动，而不是因为“CanonicalUserId”听起来高级就先造一套大系统。&lt;/p&gt;
&lt;h1 id="6-如何在自己的数据平台中落地"&gt;6. 如何在自己的数据平台中落地
&lt;/h1&gt;&lt;h2 id="61-先写-identity-contract"&gt;6.1 先写 Identity Contract
&lt;/h2&gt;&lt;p&gt;在建表和写 Flink 任务之前，先由产品、账号、数据、隐私和安全团队共同确认一份身份契约：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CanonicalUserId 代表什么实体，在哪些租户和业务域内唯一？&lt;/li&gt;
&lt;li&gt;有哪些 Namespace，格式、大小写和标准化规则是什么？&lt;/li&gt;
&lt;li&gt;哪些标识是强标识，哪些可能共享或回收？&lt;/li&gt;
&lt;li&gt;哪些事件可以创建绑定，客户端自报是否可信？&lt;/li&gt;
&lt;li&gt;一个 Profile 允许出现多少个同类标识？&lt;/li&gt;
&lt;li&gt;冲突时按来源、时间还是人工结果决定？&lt;/li&gt;
&lt;li&gt;合并和拆分是否需要回算历史，保留多久？&lt;/li&gt;
&lt;li&gt;哪些标识属于 PII，谁可以查询和导出？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;规则不明确时，技术系统只会更快地产生不明确的结果。&lt;/p&gt;
&lt;h2 id="62-推荐的数据模型"&gt;6.2 推荐的数据模型
&lt;/h2&gt;&lt;p&gt;一行同时放 &lt;code&gt;canonical_user_id + user_id + device_id&lt;/code&gt; 无法表达一对多、来源、有效期和解绑。更可扩展的模型至少包含以下几类表：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;35
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;36
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;37
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;38
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;39
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;40
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;41
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;42
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;43
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;44
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;45
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;46
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;47
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;48
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; identity_node (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; node_id BIGINT &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; namespace VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; identifier_token VARCHAR(&lt;span style="color:#ae81ff"&gt;128&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pii_class VARCHAR(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; first_seen_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; last_seen_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;UNIQUE&lt;/span&gt; (namespace, identifier_token)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; identity_edge (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; edge_id BIGINT &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; left_node_id BIGINT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; right_node_id BIGINT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; evidence_type VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source_system VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source_event_id VARCHAR(&lt;span style="color:#ae81ff"&gt;128&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; confidence DECIMAL(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; valid_from &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; valid_to &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status VARCHAR(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; rule_version VARCHAR(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; canonical_profile (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; canonical_user_id VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; entity_type VARCHAR(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; profile_status VARCHAR(&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; created_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; updated_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; profile_membership (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; node_id BIGINT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; canonical_user_id VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; valid_from &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; valid_to &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mapping_version BIGINT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; is_current BOOLEAN &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt; (node_id, canonical_user_id, mapping_version)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; profile_redirect (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; retired_user_id VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; survivor_user_id VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reason VARCHAR(&lt;span style="color:#ae81ff"&gt;128&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; created_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;其中 &lt;code&gt;identifier_token&lt;/code&gt; 可以是受控的内部 Token，或由密钥保护的 HMAC。普通无盐哈希仍容易遭受邮箱、手机号字典枚举，不能替代访问控制和加密。&lt;/p&gt;
&lt;h2 id="63-数据处理链路"&gt;6.3 数据处理链路
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 S["Web / App / Server / CRM"] --&gt; R["不可变原始事件"]
 R --&gt; N["Namespace 校验与标准化"]
 N --&gt; Q{"质量与冲突检查"}
 Q --&gt;|通过| E["Identity Edge Ledger"]
 Q --&gt;|异常| X["隔离与人工处理"]
 E --&gt; G["Identity Resolution Engine"]
 G --&gt; H["当前映射 + 历史版本"]
 H --&gt; K["在线 KV / Profile API"]
 H --&gt; W["数仓宽表 / BI / 标签"]
 G --&gt; A["合并决策与审计日志"]&lt;/pre&gt;&lt;p&gt;核心原则是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原始事件保持不可变。&lt;/li&gt;
&lt;li&gt;标识关系采用追加式事实和有效期，而不是直接覆盖。&lt;/li&gt;
&lt;li&gt;当前映射和历史映射同时保留。&lt;/li&gt;
&lt;li&gt;在线服务与离线分析可以共享规则，但使用不同的物化形式。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="64-一次解析决策的基本步骤"&gt;6.4 一次解析决策的基本步骤
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;1. 从事件中提取所有已声明的 Namespace 和标识值
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2. 标准化并过滤空值、占位符、测试值和非法格式
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;3. 查找每个标识当前所属的 Profile
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;4. 若均未命中，创建新 Profile
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;5. 若只命中一个，检查数量和唯一性约束后添加成员
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;6. 若命中多个，按证据、来源和优先级判断是否允许合并
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;7. 冲突时拒绝该边或进入隔离队列
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8. 记录决策、规则版本和来源事件
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;9. 更新受影响 Profile 的成员关系和在线缓存
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;登录关系最好由服务端认证成功事件确认，而不是完全信任客户端传来的任意 &lt;code&gt;user_id&lt;/code&gt;。否则攻击者或埋点错误可能把一个设备绑定到其他账号。&lt;/p&gt;
&lt;h2 id="65-合并容易拆分更难"&gt;6.5 合并容易，拆分更难
&lt;/h2&gt;&lt;p&gt;Union-Find（并查集）非常适合只增加边的离线合并，但不擅长删除边。一旦发生误绑或手机号回收，需要拆分身份簇时，单纯并查集很难恢复原关系。&lt;/p&gt;
&lt;p&gt;可撤销系统通常会：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;保留不可变的边和决策日志。&lt;/li&gt;
&lt;li&gt;将错误边标记为失效，而不是物理删除。&lt;/li&gt;
&lt;li&gt;从受影响连通子图重新计算 Profile。&lt;/li&gt;
&lt;li&gt;生成新的 Membership 版本。&lt;/li&gt;
&lt;li&gt;通过 Redirect 或变更日志通知下游更新。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这也是为什么“支持解绑”不是多写一个 &lt;code&gt;DELETE&lt;/code&gt; 接口，而是一项涉及历史、缓存、画像、分群和下游导出的系统能力。&lt;/p&gt;
&lt;h2 id="66-实时链路与离线链路"&gt;6.6 实时链路与离线链路
&lt;/h2&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;登录后立即拿到统一 Profile，用于个性化和风控&lt;/td&gt;
 &lt;td&gt;Kafka/Flink + KV/缓存 + 规则服务&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;离线重算&lt;/td&gt;
 &lt;td&gt;处理迟到数据、规则升级、合并和拆分&lt;/td&gt;
 &lt;td&gt;Spark/Hive/图计算 + 版本化结果表&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;在线查询&lt;/td&gt;
 &lt;td&gt;按任一标识查当前 CanonicalUserId&lt;/td&gt;
 &lt;td&gt;Profile API、KV 索引&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;历史分析&lt;/td&gt;
 &lt;td&gt;按当前映射或 As-of 映射统计&lt;/td&gt;
 &lt;td&gt;数仓 Membership 快照或 SCD2 表&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CanonicalUserId 不等于必须使用图数据库。若主要查询是“给定 ID 找 Profile”，关系表加 KV 索引通常已经足够；只有在风控等场景需要多跳关系遍历、路径查询和高扇出分析时，图数据库的价值才明显。&lt;/p&gt;
&lt;h2 id="67-必须监控什么"&gt;6.7 必须监控什么
&lt;/h2&gt;&lt;p&gt;建议至少监控：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;匿名事件占比、已解析事件占比和映射延迟。&lt;/li&gt;
&lt;li&gt;每种 Namespace 的空值率、非法率和阻断值命中率。&lt;/li&gt;
&lt;li&gt;每日新增边、合并、拒绝、冲突和解绑数量。&lt;/li&gt;
&lt;li&gt;Profile 成员数、设备数的 P95/P99 与最大值。&lt;/li&gt;
&lt;li&gt;超大连通分量数量，是否出现单点异常增长。&lt;/li&gt;
&lt;li&gt;同一 Profile 包含多个唯一账号 ID 的违规数量。&lt;/li&gt;
&lt;li&gt;使用身份归一前后的 DAU、漏斗和留存变化。&lt;/li&gt;
&lt;li&gt;规则版本发布后被重分配的事件和 Profile 数量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;同一账号登录两台设备&lt;/td&gt;
 &lt;td&gt;两台设备可解析到同一账号 Profile&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;两个账号先后使用共享设备&lt;/td&gt;
 &lt;td&gt;两个账号不被设备节点合并&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;退出后新用户注册&lt;/td&gt;
 &lt;td&gt;新用户的匿名行为不归到旧账号&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&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;user_id=null&lt;/code&gt; 或测试占位值&lt;/td&gt;
 &lt;td&gt;不参与合并并触发告警&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;错误绑定后解绑&lt;/td&gt;
 &lt;td&gt;受影响身份簇可拆分并生成新版本&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;迟到登录事件到达&lt;/td&gt;
 &lt;td&gt;当前视图更新，原始事件保持不变&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="68-隐私和安全不是附加项"&gt;6.8 隐私和安全不是附加项
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;只采集业务确实需要的标识，并记录用途和同意状态。&lt;/li&gt;
&lt;li&gt;PII 与普通分析字段分层存储，使用加密、Tokenization 和最小权限。&lt;/li&gt;
&lt;li&gt;不把邮箱、手机号直接编码进 CanonicalUserId。&lt;/li&gt;
&lt;li&gt;Profile API 只允许服务端访问，避免在客户端暴露可枚举的身份查询能力。&lt;/li&gt;
&lt;li&gt;删除请求需要传播到节点、Profile、画像、导出和备份策略中。&lt;/li&gt;
&lt;li&gt;跨地区、跨产品合并前确认数据驻留和用途限制。&lt;/li&gt;
&lt;li&gt;概率匹配结果必须可解释、可申诉，不能直接用于高风险自动决策。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="7-主流平台和大规模系统的公开实现"&gt;7. 主流平台和大规模系统的公开实现
&lt;/h1&gt;&lt;p&gt;以下内容基于截至 &lt;strong&gt;2026-08-21&lt;/strong&gt; 可访问的官方文档。各平台解决的问题和术语并不完全相同，不能把某一家的实现直接当成行业标准。&lt;/p&gt;
&lt;h2 id="71-mixpanel产品分析中的事件流拼接"&gt;7.1 Mixpanel：产品分析中的事件流拼接
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://docs.mixpanel.com/docs/tracking-methods/id-management/identifying-users-simplified" target="_blank" rel="noopener"
 &gt;Mixpanel Simplified ID Merge&lt;/a&gt; 使用 &lt;code&gt;$device_id&lt;/code&gt; 和 &lt;code&gt;$user_id&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;匿名事件只有 &lt;code&gt;$device_id&lt;/code&gt; 时，以设备标识作为 &lt;code&gt;distinct_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;首次收到同时包含 &lt;code&gt;$device_id&lt;/code&gt; 与 &lt;code&gt;$user_id&lt;/code&gt; 的事件时创建身份簇。&lt;/li&gt;
&lt;li&gt;Simplified API 中 canonical &lt;code&gt;distinct_id&lt;/code&gt; 固定为 &lt;code&gt;$user_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;可以用簇内任一 ID 写入，但查询和导出使用 canonical ID。&lt;/li&gt;
&lt;li&gt;Simplified API 不允许合并两个不同的 &lt;code&gt;$user_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;官方建议在退出或认证超时时调用 &lt;code&gt;.reset()&lt;/code&gt;，避免共享设备误合并。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a class="link" href="https://docs.mixpanel.com/docs/tracking-methods/id-management/identifying-users-original" target="_blank" rel="noopener"
 &gt;Mixpanel Original ID Merge&lt;/a&gt; 则不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$identify&lt;/code&gt; 只接受 UUIDv4 匿名 ID，且该匿名 ID 不能已属于其他 identified ID。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$merge&lt;/code&gt; 可以合并任意两个 ID，但身份簇不能超过 500 个 ID。&lt;/li&gt;
&lt;li&gt;canonical &lt;code&gt;distinct_id&lt;/code&gt; 由平台从簇内选择，未必是业务 &lt;code&gt;user_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Original 文档对 &lt;code&gt;reset()&lt;/code&gt; 的建议也更偏向“明确要切换使用者时再调用”。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原文将 UUIDv4、500-ID 上限、canonical 选择和 Simplified 流程写在了一起，实际上这些约束分属不同版本。&lt;/p&gt;
&lt;h2 id="72-amplitude生成内部-amplitude-id"&gt;7.2 Amplitude：生成内部 Amplitude ID
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://amplitude.com/docs/data/sources/instrument-track-unique-users" target="_blank" rel="noopener"
 &gt;Amplitude 的唯一用户文档&lt;/a&gt; 描述了三层标识：Device ID、User ID 和平台生成的 Amplitude ID。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;匿名事件先按 Device ID 获得 Amplitude ID。&lt;/li&gt;
&lt;li&gt;同一设备后来出现 User ID 时，匿名事件可与已识别用户合并。&lt;/li&gt;
&lt;li&gt;同一 User ID 出现在多个设备上时，平台优先按 User ID 归一。&lt;/li&gt;
&lt;li&gt;Amplitude 不能合并两个不同的 User ID；更换 User ID 会被视为不同用户。&lt;/li&gt;
&lt;li&gt;共享设备上的后续匿名事件默认归给最后一个已知用户，除非将 User ID 设为 &lt;code&gt;null&lt;/code&gt; 并生成新的 Device ID。&lt;/li&gt;
&lt;li&gt;合并映射会在分析查询中生效，但导出到 Redshift 的原始事件不可变，使用原始数据计算 DAU 可能与平台界面不同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个例子很好地说明了“统一查询视图”和“改写原始事件”不是同一回事。&lt;/p&gt;
&lt;h2 id="73-segment-unify持久-id-加合并保护"&gt;7.3 Segment Unify：持久 ID 加合并保护
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://www.twilio.com/docs/segment/unify/identity-resolution/" target="_blank" rel="noopener"
 &gt;Twilio Segment Unify&lt;/a&gt; 将 Cookie、设备、邮箱、&lt;code&gt;userId&lt;/code&gt; 和自定义 External ID 连接到一个持久内部 ID，并公开了比较完整的治理规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新事件匹配零个 Profile 时创建，匹配一个时追加，匹配多个时尝试合并。&lt;/li&gt;
&lt;li&gt;可为标识设置阻断值、数量限制和优先级。&lt;/li&gt;
&lt;li&gt;默认 &lt;code&gt;user_id&lt;/code&gt; 限制为一个，其他标识默认允许多个。&lt;/li&gt;
&lt;li&gt;高优先级稳定 ID 可以阻止低优先级邮箱或匿名 ID 把两个账号错误合并。&lt;/li&gt;
&lt;li&gt;Merge Protection 专门处理共享设备、公共匿名 ID 等问题。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Segment 不只是“收集后转发数据的管道”。它的 Connections 偏采集和路由，而 Unify 提供 Identity Resolution、Profile 和受众能力，两者要分层理解。&lt;/p&gt;
&lt;h2 id="74-adobe-experience-platform防止-graph-collapse"&gt;7.4 Adobe Experience Platform：防止 Graph Collapse
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://experienceleague.adobe.com/en/docs/experience-platform/identity/features/identity-graph-linking-rules/overview" target="_blank" rel="noopener"
 &gt;Adobe Identity Graph Linking Rules&lt;/a&gt; 将生产级保护明确抽象为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unique Namespace&lt;/strong&gt;：一个身份图中某类标识最多只能有一个，例如 CRMID。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Namespace Priority&lt;/strong&gt;：冲突时稳定账号标识优先于设备或浏览器标识。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identity Optimization Algorithm&lt;/strong&gt;：违反唯一性约束时重放边，并移除导致两个 Person 合并的低优先级关系。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在共享平板上先后登录两个 CRMID 时，Adobe 不会简单通过共同 ECID 把两人合并，而会拆成两个图。公开文档中的默认匿名归属采用“最后一个已认证用户”逻辑，这也再次说明“匿名行为归第一个用户”不是行业通则。&lt;/p&gt;
&lt;h2 id="75-神策-id-mapping-30显式-id-key-与绑定解绑"&gt;7.5 神策 ID-Mapping 3.0：显式 ID Key 与绑定/解绑
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://manual.sensorsdata.cn/sa/docs/idm_sdk_intro/v0204" target="_blank" rel="noopener"
 &gt;神策全域用户关联文档&lt;/a&gt; 的公开机制包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;预先定义参与关联的 ID Key，明确手机号、邮箱、OpenID 等标识语义。&lt;/li&gt;
&lt;li&gt;事件包含未定义的 ID Key 时拒绝入库，避免含义不明的标识进入图谱。&lt;/li&gt;
&lt;li&gt;客户端通过 &lt;code&gt;loginWithKey&lt;/code&gt;、&lt;code&gt;bind&lt;/code&gt;、&lt;code&gt;unbind&lt;/code&gt; 建立或解除关系。&lt;/li&gt;
&lt;li&gt;客户端绑定结果会进入本地缓存，后续事件携带相关 ID 信息。&lt;/li&gt;
&lt;li&gt;服务端 SDK 发送绑定事件，但不会把绑定 ID 持久化到服务端 SDK 本地状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;神策公开的&lt;a class="link" href="https://www.sensorsdata.cn/blog/20220518" target="_blank" rel="noopener"
 &gt;全域用户关联服务&lt;/a&gt;还强调来源可信度差异、历史数据治理以及手机号等标识发生变更时的绑定和解绑。这比“一台设备的匿名行为永远给首个登录用户”更接近真实治理过程。&lt;/p&gt;
&lt;h2 id="76-aws-entity-resolution规则机器学习与匹配组"&gt;7.6 AWS Entity Resolution：规则、机器学习与匹配组
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://docs.aws.amazon.com/entityresolution/latest/userguide/what-is-service.html" target="_blank" rel="noopener"
 &gt;AWS Entity Resolution&lt;/a&gt; 更接近跨数据源实体解析，而不是客户端埋点 SDK：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对输入进行 Schema Mapping 和标准化。&lt;/li&gt;
&lt;li&gt;支持基于规则、机器学习和数据供应商的匹配流程。&lt;/li&gt;
&lt;li&gt;规则匹配输出命中的规则编号，机器学习匹配输出 0 到 1 的置信度。&lt;/li&gt;
&lt;li&gt;支持批处理、规则场景的增量处理，以及通过 &lt;code&gt;GetMatchId&lt;/code&gt; 进行近实时查找。&lt;/li&gt;
&lt;li&gt;ID Mapping Workflow 可把 Source Namespace 翻译到 Target Namespace。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它说明了当问题从“登录前后是否同一账号”扩展到“两个 CRM 记录是否属于同一客户”时，系统需要标准化、匹配规则、置信度和工作流，而不只是 &lt;code&gt;identify()&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="77-airbnb大规模关系图基础设施"&gt;7.7 Airbnb：大规模关系图基础设施
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://airbnb.tech/infrastructure/scaling-airbnbs-identity-graph-with-a-unified-knowledge-graph-infrastructure/" target="_blank" rel="noopener"
 &gt;Airbnb 公开的 Identity Graph 架构&lt;/a&gt;主要服务 Trust &amp;amp; Safety，不等同于产品分析中的 CanonicalUserId，但展示了大厂在多跳关系查询下的实现方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;图中约有 70 亿节点、110 亿条边，每天增加约 500 万条边。&lt;/li&gt;
&lt;li&gt;大部分数据通过异步事件近实时写入，在线服务进行低延迟查询。&lt;/li&gt;
&lt;li&gt;存储采用 JanusGraph + DynamoDB，OpenSearch 建索引，前面还有 KV 缓存。&lt;/li&gt;
&lt;li&gt;典型查询需要 4 到 8 跳，重点处理高扇出、长尾延迟和稳定性。&lt;/li&gt;
&lt;li&gt;通过事件写入、批量加载、在线服务和复杂查询预计算等多个应用分离读写职责。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个案例的启示不是“做 CanonicalUserId 必须上 JanusGraph”，而是：当需求从单跳 ID 映射升级为账号、设备、支付工具和风险关系的多跳遍历时，图存储、缓存、预计算和查询治理才会成为核心问题。&lt;/p&gt;
&lt;h2 id="78-这些公开实现的共同模式"&gt;7.8 这些公开实现的共同模式
&lt;/h2&gt;&lt;p&gt;尽管产品不同，它们大体都在做以下几件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为标识声明类型和语义，而不是只存一个字符串。&lt;/li&gt;
&lt;li&gt;以登录、绑定、主数据或匹配结果作为关联证据。&lt;/li&gt;
&lt;li&gt;使用内部 Profile ID 或 canonical ID 隔离外部标识变化。&lt;/li&gt;
&lt;li&gt;为共享设备、公共值和异常高扇出设置合并保护。&lt;/li&gt;
&lt;li&gt;保留原始数据，通过映射层提供当前统一视图。&lt;/li&gt;
&lt;li&gt;支持冲突检测、解绑、版本或局部重算。&lt;/li&gt;
&lt;li&gt;同时服务实时 Profile 查询和离线分析。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="8-一条适合从零开始的建设路径"&gt;8. 一条适合从零开始的建设路径
&lt;/h1&gt;&lt;h2 id="81-第一阶段只解决匿名到登录"&gt;8.1 第一阶段：只解决匿名到登录
&lt;/h2&gt;&lt;p&gt;最小可用方案不需要图数据库：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;业务账号使用永不复用的数据库 ID，不用邮箱作为 &lt;code&gt;user_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Web/App 为匿名访问生成随机 &lt;code&gt;anonymous_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;每条事件保留事件发生时的 &lt;code&gt;anonymous_id&lt;/code&gt; 和可选 &lt;code&gt;account_id&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;只有认证成功事件可以建立二者的确定性边。&lt;/li&gt;
&lt;li&gt;数仓生成 &lt;code&gt;anonymous_id -&amp;gt; canonical_user_id&lt;/code&gt; 的当前映射。&lt;/li&gt;
&lt;li&gt;原始事件不改写，分析层按映射版本补充 CanonicalUserId。&lt;/li&gt;
&lt;li&gt;先覆盖同设备注册、跨设备登录、退出和共享设备测试。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这一阶段已经能改善注册漏斗、跨设备 AU 和留存。&lt;/p&gt;
&lt;h2 id="82-第二阶段增加多来源标识与治理"&gt;8.2 第二阶段：增加多来源标识与治理
&lt;/h2&gt;&lt;p&gt;当 CRM、小程序、门店和客服系统接入后，再增加：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Namespace 注册表和标准化规则。&lt;/li&gt;
&lt;li&gt;来源可信度、唯一性、数量和时间窗口约束。&lt;/li&gt;
&lt;li&gt;Edge Ledger、Profile Membership 和 Redirect 表。&lt;/li&gt;
&lt;li&gt;冲突隔离、解绑、局部重算和审计界面。&lt;/li&gt;
&lt;li&gt;当前视图与 As-of 历史视图。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="83-第三阶段按需要增加实时和概率能力"&gt;8.3 第三阶段：按需要增加实时和概率能力
&lt;/h2&gt;&lt;p&gt;只有在业务需要实时个性化或风控时，才把解析结果同步到 KV/Profile API；只有在缺少确定性键、确实需要跨系统客户去重时，才引入规则评分或机器学习匹配。&lt;/p&gt;
&lt;p&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;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;产品必须登录才能使用&lt;/td&gt;
 &lt;td&gt;直接使用稳定 &lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;需要分析注册前后的行为&lt;/td&gt;
 &lt;td&gt;匿名 ID + 确定性登录映射&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;多终端、多 CRM、需要解绑&lt;/td&gt;
 &lt;td&gt;版本化 Identity Graph&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;姓名地址相似但无共同 ID&lt;/td&gt;
 &lt;td&gt;Entity Resolution + 置信度&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;风控需要多跳关系查询&lt;/td&gt;
 &lt;td&gt;图数据库、缓存与预计算&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id="9-对原文几个结论的集中修正"&gt;9. 对原文几个结论的集中修正
&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;**CanonicalUserId 不保证对应唯一自然人。**它对应的是业务定义和规则版本下的实体，可能是账号或 Profile。&lt;/li&gt;
&lt;li&gt;**不是所有 ID 都应使用 UUIDv4。**匿名 ID 常用 UUID；业务账号可以使用稳定、不可复用的内部主键。Mixpanel Original 的 UUIDv4 是特定 API 约束。&lt;/li&gt;
&lt;li&gt;**一个 CanonicalUserId 能否包含多个 &lt;code&gt;user_id&lt;/code&gt; 取决于系统规则。**Mixpanel Simplified 不允许合并两个 &lt;code&gt;$user_id&lt;/code&gt;，Segment 等系统则可以配置不同限制。&lt;/li&gt;
&lt;li&gt;**共享设备的匿名行为没有统一行业归属。**有的平台采用最近一次认证用户，有的平台要求 reset，也可以选择保持未解析。&lt;/li&gt;
&lt;li&gt;**“匿名行为归首个登录用户”不是通用最佳实践。**它会在共享设备和账号切换中造成错误归属。&lt;/li&gt;
&lt;li&gt;**500 个 ID 的簇限制不是 CanonicalUserId 的普遍限制。**它来自 Mixpanel Original API 的具体实现。&lt;/li&gt;
&lt;li&gt;**&lt;code&gt;reset()&lt;/code&gt; 策略必须结合厂商版本和业务状态机。**Simplified、Original、Amplitude 的建议并不相同。&lt;/li&gt;
&lt;li&gt;**一张以 &lt;code&gt;canonical_user_id&lt;/code&gt; 为主键的扁平映射表不够。**生产系统还需要节点、边、有效期、来源、版本和重定向。&lt;/li&gt;
&lt;li&gt;**Segment 不只是数据管道。**其 Connections、Unify 和 Engage 分别覆盖采集路由、身份解析/Profile 与受众激活能力。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id="10-总结"&gt;10. 总结
&lt;/h1&gt;&lt;p&gt;CanonicalUserId 表面上是“给多个 ID 找一个主 ID”，更深一层其实是一个数据治理问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先定义要统一的实体，而不是先挑一个字段。&lt;/li&gt;
&lt;li&gt;用 Namespace 区分标识语义，用证据边描述为什么关联。&lt;/li&gt;
&lt;li&gt;强标识和弱标识采用不同规则，宁可暂不合并，也不要错误合并。&lt;/li&gt;
&lt;li&gt;CanonicalUserId 最好与外部账号、邮箱和设备解耦。&lt;/li&gt;
&lt;li&gt;原始事件不可变，映射关系要有来源、有效期、版本和审计。&lt;/li&gt;
&lt;li&gt;合并之外还要设计冲突、解绑、拆分、回算和删除。&lt;/li&gt;
&lt;li&gt;简单 ID-Mapping 不需要图数据库，多跳关系查询才需要图基础设施。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最终，CanonicalUserId 不是一张“身份证”，而是系统在特定业务边界、证据和规则下，对“这些记录当前属于同一实体”做出的可追溯判断。&lt;/p&gt;</description></item><item><title>【工作杂谈_20260810】用户分析与身份体系: Active User：定义、计算口径与常见应用</title><link>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/useranalyticsidentity/20260810_%E7%94%A8%E6%88%B7%E5%88%86%E6%9E%90%E4%B8%8E%E8%BA%AB%E4%BB%BD%E4%BD%93%E7%B3%BB_activeuser%E6%8C%87%E6%A0%87%E5%8F%A3%E5%BE%84/</link><pubDate>Mon, 10 Aug 2026 23:58:04 +0800</pubDate><guid>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/useranalyticsidentity/20260810_%E7%94%A8%E6%88%B7%E5%88%86%E6%9E%90%E4%B8%8E%E8%BA%AB%E4%BB%BD%E4%BD%93%E7%B3%BB_activeuser%E6%8C%87%E6%A0%87%E5%8F%A3%E5%BE%84/</guid><description>&lt;h1 id="1-active-user-是什么"&gt;1. Active User 是什么
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;Active User，简称 AU，表示在指定时间范围内，完成了某种“有效行为”的去重用户数。&lt;/strong&gt;&lt;/p&gt;
&lt;p&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;“谁”算一个用户&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;UserId&lt;/code&gt;、&lt;code&gt;CookieId&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;时间窗口&lt;/td&gt;
 &lt;td&gt;在什么时间内活跃&lt;/td&gt;
 &lt;td&gt;一天Daily、一周Weekly、一个月Monthly&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;活跃行为&lt;/td&gt;
 &lt;td&gt;做了什么才算活跃&lt;/td&gt;
 &lt;td&gt;打开 App、阅读内容、观看超过 N 秒、完成购买&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;因此，一个完整定义应该是：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;DAU = 在某自然日内，至少发生一次符合活跃条件事件的去重用户数。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;通用计算形式为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CASE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;WHEN&lt;/span&gt; is_active_event &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;THEN&lt;/span&gt; user_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;END&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在不同系统中，“Active”口径可能不同。例如，官方定义中，活跃用户通常强调用户在指定日期范围内发生了符合参与条件的行为，而总用户只要求触发过事件。因此，在业务数据场景中不能只写“AU”，还必须同时说明活跃事件、统计窗口和用户标识。 &lt;a class="link" href="https://support.google.com/analytics/answer/12253918?hl=en" target="_blank" rel="noopener"
 &gt;[support.google.com]&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="11-dauwaumau"&gt;1.1 DAU、WAU、MAU
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DAU&lt;/strong&gt;：Daily Active Users，日活跃用户数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WAU&lt;/strong&gt;：Weekly Active Users，周活跃用户数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MAU&lt;/strong&gt;：Monthly Active Users，月活跃用户数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;假设同一个用户：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;8 月 1 日活跃&lt;/li&gt;
&lt;li&gt;8 月 2 日活跃&lt;/li&gt;
&lt;li&gt;8 月 10 日活跃&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;8 月 1 日 DAU：计数 1&lt;/li&gt;
&lt;li&gt;8 月 2 日 DAU：计数 1&lt;/li&gt;
&lt;li&gt;8 月 1 日至 7 日 WAU：仍然只计数 1（因为这一周出现的是同一个用户）&lt;/li&gt;
&lt;li&gt;8 月 MAU：仍然只计数 1（因为这一个月出现的是同一个用户）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;WAU 不能通过七天 DAU 相加得到，MAU 也不能通过每天 DAU 相加得到。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;因为 AU 本质上是：集合中不同用户的数量，而不是事件数量。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="2-au在大数据场景中的常见应用"&gt;2. AU在大数据场景中的常见应用
&lt;/h1&gt;&lt;p&gt;以AU为基础可以扩展出&lt;strong&gt;多维分析、漏斗对比、留存计算&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多维分析&lt;/strong&gt;：如你之前所示，按市场、设备等维度拆分 AU，了解不同分群的用户活跃情况。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;漏斗对比&lt;/strong&gt;：比较不同渠道的 DAU 变化，辅助运营决策。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;留存计算&lt;/strong&gt;：基于每日 AU 计算次日/7日/30日留存率。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="21-多维分析不同分群的用户活跃情况"&gt;2.1 多维分析：不同分群的用户活跃情况
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;数字产品的用户生命周期分析框架如下&lt;/strong&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;strong&gt;用户画像&lt;/strong&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;strong&gt;用户获取&lt;/strong&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;strong&gt;用户行为&lt;/strong&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;strong&gt;用户体验&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;用户在不同版本下的体验如何？&lt;/td&gt;
 &lt;td&gt;APP版本&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;商业表现&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;用户在哪些市场/区域贡献价值？&lt;/td&gt;
 &lt;td&gt;市场、国家&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;多维 AU 分析，是把整体活跃用户集合按照不同维度进行切分，观察不同用户群体、业务区域或内容类型的活跃表现。&lt;/p&gt;
&lt;p&gt;——这个思路在移动应用和广告分析领域是&lt;strong&gt;通用的分析方法&lt;/strong&gt;，各家平台都会提供类似的维度：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;百度移动统计&lt;/strong&gt;的常用维度就包含：品牌、设备型号、操作系统、运营商、、渠道、地域（国家/省/市）等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Apple App Store Connect&lt;/strong&gt;​ 的分析维度包含：App Version、Platform Version、Region、Product Page 等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Firebase / Google Mobile Ads SDK&lt;/strong&gt;​ 自动采集的用户属性包含：国家、设备品牌、设备类别、应用版本、操作系统版本、兴趣类别等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Braze&lt;/strong&gt;​ 等用户参与平台也按设备类型、平台、内容互动情况等维度评估活跃用户&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="211-基本计算方式"&gt;2.1.1 基本计算方式
&lt;/h3&gt;&lt;p&gt;假设原始行为数据如下：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;UserId&lt;/th&gt;
 &lt;th&gt;Date&lt;/th&gt;
 &lt;th&gt;Market&lt;/th&gt;
 &lt;th&gt;Device&lt;/th&gt;
 &lt;th&gt;Content&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;U001&lt;/td&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;US&lt;/td&gt;
 &lt;td&gt;Mobile&lt;/td&gt;
 &lt;td&gt;C001&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;U001&lt;/td&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;US&lt;/td&gt;
 &lt;td&gt;PC&lt;/td&gt;
 &lt;td&gt;C002&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;U002&lt;/td&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;US&lt;/td&gt;
 &lt;td&gt;Mobile&lt;/td&gt;
 &lt;td&gt;C001&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;U003&lt;/td&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;UK&lt;/td&gt;
 &lt;td&gt;Mobile&lt;/td&gt;
 &lt;td&gt;C003&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;按 &lt;code&gt;Market + Device&lt;/code&gt; 计算 DAU：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; Date, Market, Device, &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; UserId) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; DAU
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;table&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GROUP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;BY&lt;/span&gt; Date, Market, Device;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Date&lt;/th&gt;
 &lt;th&gt;Market&lt;/th&gt;
 &lt;th&gt;Device&lt;/th&gt;
 &lt;th&gt;DAU&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;US&lt;/td&gt;
 &lt;td&gt;Mobile&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;US&lt;/td&gt;
 &lt;td&gt;PC&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;UK&lt;/td&gt;
 &lt;td&gt;Mobile&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这里可以回答：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;US 市场 Mobile 用户有多少？——2个&lt;/li&gt;
&lt;li&gt;不同设备上的用户活跃分布如何？——Mobile的用户活跃度高于PC设备&lt;/li&gt;
&lt;li&gt;某个 Market 的 DAU 下跌是否集中在某种设备？——由于目前只有一天的数据，暂时看不出下跌&lt;/li&gt;
&lt;li&gt;某个 类型文章 的活跃用户增长主要来自哪个 Market？——由于目前只有一天的数据，暂时看不出下跌&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="212-多维-au-不能直接上卷"&gt;2.1.2 多维 AU 不能直接上卷
&lt;/h3&gt;&lt;p&gt;上面的三个分组不能直接相加得到整体 AU：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;US Mobile AU &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;US PC AU &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;不能推导：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;US Overall AU &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;因为 &lt;code&gt;U001&lt;/code&gt; 同时使用了 Mobile 和 PC。&lt;/p&gt;
&lt;p&gt;正确结果是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;US 用户集合：
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Mobile = {U001, U002}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PC = {U001}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;并集 = {U001, U002}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;US Overall AU = 2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;用集合表达：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AU(Mobile ∪ PC) &amp;lt;= AU(Mobile) + AU(PC)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;只有在能够保证用户不会跨分组出现时，分组 AU 才能直接相加。但实际的 市场/设备/内容/供应商 场景通常不能保证这一点。&lt;/p&gt;
&lt;h3 id="213-什么时候可以直接相加"&gt;2.1.3 什么时候可以直接相加
&lt;/h3&gt;&lt;p&gt;结合着两个指标理解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;浏览量PageViewCount：发生了多少次内容消费行为&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;活跃用户数PageViewAUCount：有多少个不同用户发生过内容消费行为&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Partner&lt;/th&gt;
 &lt;th&gt;Content&lt;/th&gt;
 &lt;th&gt;PageViewCount&lt;/th&gt;
 &lt;th&gt;PageViewAUCount&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Partner A&lt;/td&gt;
 &lt;td&gt;C001&lt;/td&gt;
 &lt;td&gt;10,000&lt;/td&gt;
 &lt;td&gt;2,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Partner A&lt;/td&gt;
 &lt;td&gt;C002&lt;/td&gt;
 &lt;td&gt;8,000&lt;/td&gt;
 &lt;td&gt;1,500&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;PageViewCount&lt;/code&gt; 可以反映内容被消费了多少次；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;PageViewAUCount&lt;/code&gt; 可以反映内容覆盖了多少不同用户。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但 Partner A 的整体 AU 不能计算为&lt;code&gt;2,000 + 1,500 = 3,500&lt;/code&gt;，因为同一个用户可能同时看过 C001 和 C002。&lt;/p&gt;
&lt;p&gt;正确方式是基于 Partner A 范围内的明细重新去重：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; date,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; partner,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; user_id) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; partner_au
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; user_event
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WHERE&lt;/span&gt; is_active_event &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GROUP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;BY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; date,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; partner;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这也解释了为什么数据模型中需要同时保存：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;叶子级别的 Content AU&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content = All&lt;/code&gt; 的 Partner AU&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Market = All&lt;/code&gt;、&lt;code&gt;Device = All&lt;/code&gt; 等不同上卷组合的 AU&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些 &lt;code&gt;All&lt;/code&gt; 粒度的 AU 不是由叶子行求和得到的，而是针对对应维度组合重新去重计算出来的&lt;/p&gt;
&lt;h3 id="214-多维分析的业务价值"&gt;2.1.4 多维分析的业务价值
&lt;/h3&gt;&lt;p&gt;多维 AU 分析主要用于定位“整体变化来自哪里”。&lt;/p&gt;
&lt;p&gt;例如整体 DAU 下降 15%，继续拆分可能发现：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;整体 DAU -15%
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── US Market -3%
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── UK Market -2%
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── DE Market -40%
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ├── Mobile -5%
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └── PC -65%
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这样可以将问题进一步定位到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;特定市场&lt;/li&gt;
&lt;li&gt;特定设备&lt;/li&gt;
&lt;li&gt;特定版本&lt;/li&gt;
&lt;li&gt;特定渠道&lt;/li&gt;
&lt;li&gt;特定内容类型&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果 PV （浏览量）未明显下降，但 AU （活跃用户数）大幅下降，需要重点检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户标识是否缺失&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UserId&lt;/code&gt; 生成逻辑是否变化&lt;/li&gt;
&lt;li&gt;去重逻辑是否变化&lt;/li&gt;
&lt;li&gt;某些用户事件是否没有进入上游&lt;/li&gt;
&lt;li&gt;匿名用户是否被统一写成同一个默认 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="22-漏斗对比不同渠道的用户转化情况"&gt;2.2 漏斗对比：不同渠道的用户转化情况
&lt;/h2&gt;&lt;p&gt;这里需要先区分两个概念：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;只比较不同渠道的 DAU 变化，严格来说属于渠道趋势对比，不是完整的漏斗分析。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;真正的漏斗要求存在一组有先后顺序的业务阶段，例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;内容曝光
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;内容点击
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;开始阅读
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;有效阅读
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;订阅或购买
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="221-渠道-dau-对比"&gt;2.2.1 渠道 DAU 对比
&lt;/h3&gt;&lt;p&gt;如果只是比较渠道活跃情况，可以计算：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Date&lt;/th&gt;
 &lt;th&gt;Channel&lt;/th&gt;
 &lt;th&gt;DAU&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;Search&lt;/td&gt;
 &lt;td&gt;20,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;Recommendation&lt;/td&gt;
 &lt;td&gt;35,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-08-01&lt;/td&gt;
 &lt;td&gt;Direct&lt;/td&gt;
 &lt;td&gt;10,000&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这可以回答：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;哪个渠道带来的活跃用户最多？&lt;/li&gt;
&lt;li&gt;某个渠道 DAU 是否持续增长？&lt;/li&gt;
&lt;li&gt;一次运营活动是否带来了更多活跃用户？&lt;/li&gt;
&lt;li&gt;渠道增长是新增用户增长，还是老用户回流？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但仅有 DAU 仍不能判断渠道质量。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Channel&lt;/th&gt;
 &lt;th&gt;DAU&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;A&lt;/td&gt;
 &lt;td&gt;100,000&lt;/td&gt;
 &lt;td&gt;1,000&lt;/td&gt;
 &lt;td&gt;1%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;B&lt;/td&gt;
 &lt;td&gt;20,000&lt;/td&gt;
 &lt;td&gt;800&lt;/td&gt;
 &lt;td&gt;4%&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A 的活跃规模更大，但购买转化率只有：1%&lt;/p&gt;
&lt;p&gt;B 的购买转化率是： 4%&lt;/p&gt;
&lt;p&gt;所以渠道评估通常需要同时看：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;活跃用户规模&lt;/li&gt;
&lt;li&gt;转化用户数&lt;/li&gt;
&lt;li&gt;转化率&lt;/li&gt;
&lt;li&gt;留存率&lt;/li&gt;
&lt;li&gt;人均消费深度&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="222-真正的用户漏斗"&gt;2.2.2 真正的用户漏斗
&lt;/h3&gt;&lt;p&gt;假设定义以下阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;ExposureUser&lt;/code&gt;：看到内容曝光&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ClickUser&lt;/code&gt;：点击内容&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ReadUser&lt;/code&gt;：开始阅读&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EffectiveReadUser&lt;/code&gt;：阅读超过 N 秒&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SubscribeUser&lt;/code&gt;：完成订阅&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每一步都应该按照用户去重：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;曝光用户数&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; exposure_user_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;点击用户数&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; click_user_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;有效阅读用户数&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; effective_read_user_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;订阅用户数&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; subscribe_user_id)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;阶段转化率：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;点击率 = 点击用户数 / 曝光用户数
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;有效阅读率 = 有效阅读用户数 / 点击用户数
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;订阅转化率 = 订阅用户数 / 有效阅读用户数
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;整体转化率 = 订阅用户数 / 曝光用户数
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="223-漏斗必须注意用户集合约束"&gt;2.2.3 漏斗必须注意用户集合约束
&lt;/h3&gt;&lt;p&gt;漏斗中的后一步用户，原则上应该是前一步用户集合的子集：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SubscribeUser ⊆ EffectiveReadUser
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;EffectiveReadUser ⊆ ClickUser
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ClickUser ⊆ ExposureUser
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;实际计算时，不能简单统计每天触发各事件的所有用户，否则可能出现：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;曝光用户数：1,000
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;点击用户数：1,200
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这通常说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;点击事件缺少对应曝光事件&lt;/li&gt;
&lt;li&gt;曝光事件丢失&lt;/li&gt;
&lt;li&gt;统计周期对不齐&lt;/li&gt;
&lt;li&gt;用户在统计周期之前曝光、周期内点击&lt;/li&gt;
&lt;li&gt;不同阶段用了不同的用户标识&lt;/li&gt;
&lt;li&gt;Join 条件或事件时间逻辑存在问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，严格漏斗一般需要根据 &lt;code&gt;user_id&lt;/code&gt; 和事件顺序建立用户路径。&lt;/p&gt;
&lt;h3 id="224-漏斗中的时间窗口"&gt;2.2.4 漏斗中的时间窗口
&lt;/h3&gt;&lt;p&gt;漏斗还必须定义转化窗口。&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;用户曝光后 24 小时内发生点击，点击后 7 天内发生订阅。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;如果不定义窗口，一个用户 8 月 1 日曝光、8 月 20 日订阅，是否还算同一次转化就会产生歧义。&lt;/p&gt;
&lt;p&gt;因此完整漏斗口径至少包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;漏斗起始事件&lt;/li&gt;
&lt;li&gt;后续阶段事件&lt;/li&gt;
&lt;li&gt;用户标识&lt;/li&gt;
&lt;li&gt;事件先后顺序&lt;/li&gt;
&lt;li&gt;阶段间允许的时间窗口&lt;/li&gt;
&lt;li&gt;是否允许跨天&lt;/li&gt;
&lt;li&gt;是否允许同一用户多次进入漏斗&lt;/li&gt;
&lt;li&gt;用户归属哪个渠道&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="225-渠道归因问题"&gt;2.2.5 渠道归因问题
&lt;/h3&gt;&lt;p&gt;用户可能经过多个渠道：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Search 曝光
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;→ Direct 访问
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;→ Recommendation 点击
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;→ 完成订阅
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;此时订阅算给哪个渠道，需要提前定义归因规则，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First-touch：归因给首次渠道&lt;/li&gt;
&lt;li&gt;Last-touch：归因给最后一次渠道&lt;/li&gt;
&lt;li&gt;Conversion-touch：归因给直接促成转化的渠道&lt;/li&gt;
&lt;li&gt;多触点归因：按照规则分摊&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以“比较渠道漏斗”并不只是按 &lt;code&gt;Channel&lt;/code&gt; 做一个 &lt;code&gt;GROUP BY&lt;/code&gt;，还需要保证渠道归因规则一致。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="23-留存计算次日7-日30-日留存"&gt;2.3 留存计算：次日、7 日、30 日留存
&lt;/h2&gt;&lt;p&gt;留存用于回答：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一批在某天活跃或首次活跃的用户，经过一段时间后，还有多少人再次活跃？&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;留存通常基于用户集合或 Cohort，也就是具有共同起始条件的一批用户进行计算。&lt;/p&gt;
&lt;h3 id="231-不能只使用每日-au-数值计算留存"&gt;2.3.1 不能只使用每日 AU 数值计算留存
&lt;/h3&gt;&lt;p&gt;假设：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8 月 1 日 DAU = 100
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8 月 2 日 DAU = 120
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;仅凭这两个数字，无法计算次日留存率。因为我们不知道 8 月 2 日的 120 个用户中，有多少人在 8 月 1 日也活跃。&lt;/p&gt;
&lt;p&gt;可能是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;情况 A：
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8 月 1 日用户 = {U001 ... U100}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8 月 2 日用户中有 80 人来自 8 月 1 日
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;次日留存率 = 80 / 100 = 80%
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;也可能是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;情况 B：
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8 月 2 日用户中只有 10 人来自 8 月 1 日
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;次日留存率 = 10 / 100 = 10%
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;所以更准确的表述是：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;留存基于每日活跃用户明细集合计算，而不是基于已经聚合好的每日 AU 数值计算。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h3 id="232-留存的集合公式"&gt;2.3.2 留存的集合公式
&lt;/h3&gt;&lt;p&gt;令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A0 = 基准日用户集合
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A1 = 基准日后第 1 天的活跃用户集合
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A7 = 基准日后第 7 天的活跃用户集合
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A30 = 基准日后第 30 天的活跃用户集合
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;那么：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;次日留存用户数 = |A0 ∩ A1|
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;次日留存率 = |A0 ∩ A1| / |A0|
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;7 日留存用户数 = |A0 ∩ A7|
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;7 日留存率 = |A0 ∩ A7| / |A0|
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;30 日留存用户数 = |A0 ∩ A30|
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;30 日留存率 = |A0 ∩ A30| / |A0|
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;|A0|&lt;/code&gt; 表示集合 A0 的用户数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;∩&lt;/code&gt; 表示两个用户集合的交集&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="233-新增用户留存和活跃用户留存"&gt;2.3.3 新增用户留存和活跃用户留存
&lt;/h3&gt;&lt;p&gt;这是留存分析中非常重要的口径区别。&lt;/p&gt;
&lt;h4 id="2331-新增用户留存"&gt;2.3.3.1 新增用户留存
&lt;/h4&gt;&lt;p&gt;基准用户是当天首次出现的用户：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8 月 1 日新增用户
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;→ 8 月 2 日是否再次活跃
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;→ 8 月 8 日是否再次活跃
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;→ 8 月 31 日是否再次活跃
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;适合评估：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新用户质量&lt;/li&gt;
&lt;li&gt;拉新渠道质量&lt;/li&gt;
&lt;li&gt;首次体验效果&lt;/li&gt;
&lt;li&gt;新版本 onboarding 效果&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2332-活跃用户留存"&gt;2.3.3.2 活跃用户留存
&lt;/h4&gt;&lt;p&gt;基准用户是当天所有活跃用户：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;8 月 1 日所有 AU
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;→ 后续是否再次活跃
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;适合评估：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;整体用户黏性&lt;/li&gt;
&lt;li&gt;产品持续使用情况&lt;/li&gt;
&lt;li&gt;内容用户回访情况&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两种口径不能混用。因为新增用户集合通常只是当日所有活跃用户的一个子集。&lt;/p&gt;
&lt;h3 id="234-第-7-日留存与7-日内留存"&gt;2.3.4 “第 7 日留存”与“7 日内留存”
&lt;/h3&gt;&lt;p&gt;这两个指标也不相同。&lt;/p&gt;
&lt;h4 id="2341-第-7-日留存"&gt;2.3.4.1 第 7 日留存
&lt;/h4&gt;&lt;p&gt;用户必须在基准日后的第 7 个自然日再次活跃：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cohort_date = 8 月 1 日
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;retention_date = 8 月 8 日
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="2342-7-日内留存"&gt;2.3.4.2 7 日内留存
&lt;/h4&gt;&lt;p&gt;用户在基准日后的第 1 至第 7 日内，只要任意一天回来就算留存：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A0 ∩ (A1 ∪ A2 ∪ ... ∪ A7)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;一般来说：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;7 日内留存率 &amp;gt;= 第 7 日留存率
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;因此报表中只写“7 日留存”是不够严谨的，需要说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;是第 7 日留存&lt;/li&gt;
&lt;li&gt;还是 7 日内任意一天回来&lt;/li&gt;
&lt;li&gt;还是第 7 日及以后仍有活跃&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="235-用户留存的sql-实现思路"&gt;2.3.5 用户留存的SQL 实现思路
&lt;/h3&gt;&lt;p&gt;假设先生成用户每日活跃表：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;27
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;28
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;29
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;30
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;31
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;32
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;33
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;34
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WITH&lt;/span&gt; daily_active &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; event_date,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; user_event
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;WHERE&lt;/span&gt; is_active_event &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cohort &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; event_date &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; cohort_date,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; daily_active
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;WHERE&lt;/span&gt; event_date &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;2026-08-01&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.cohort_date,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.user_id) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; cohort_users,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CASE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;WHEN&lt;/span&gt; DATEDIFF(d.event_date, &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.cohort_date) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;THEN&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.user_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;END&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; day_1_retained_users,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CASE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;WHEN&lt;/span&gt; DATEDIFF(d.event_date, &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.cohort_date) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;THEN&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.user_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;END&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; day_7_retained_users,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CASE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;WHEN&lt;/span&gt; DATEDIFF(d.event_date, &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.cohort_date) &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;THEN&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.user_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;END&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; day_30_retained_users
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; cohort &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;LEFT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;JOIN&lt;/span&gt; daily_active d
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ON&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.user_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; d.user_id
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;GROUP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;BY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;c&lt;/span&gt;.cohort_date;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后计算：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Day 1 Retention = day_1_retained_users / cohort_users
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Day 7 Retention = day_7_retained_users / cohort_users
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Day 30 Retention = day_30_retained_users / cohort_users
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="24-三类应用之间的关系"&gt;2.4 三类应用之间的关系
&lt;/h2&gt;&lt;p&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;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;活跃用户来自哪些分群&lt;/td&gt;
 &lt;td&gt;谁在活跃？&lt;br/&gt;来自哪个 Market、Device、Partner、Channel？&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;/td&gt;
 &lt;td&gt;用户在不同业务阶段流失在哪里&lt;/td&gt;
 &lt;td&gt;用户做到了哪一步？&lt;br/&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;/td&gt;
 &lt;td&gt;同一批用户后续是否再次回来&lt;/td&gt;
 &lt;td&gt;用户以后还回来吗？&lt;br/&gt;次日、第 7 日、第 30 日是否再次活跃？&lt;/td&gt;
 &lt;td&gt;在不同日期的用户集合之间求交集&lt;/td&gt;
 &lt;td&gt;跨时间集合求交集&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id="3-在数据模型中的关键设计原则"&gt;3. 在数据模型中的关键设计原则
&lt;/h1&gt;&lt;h2 id="31-保留统一的用户标识"&gt;3.1 保留统一的用户标识
&lt;/h2&gt;&lt;p&gt;如果一部分事件使用 &lt;code&gt;UserId&lt;/code&gt;，另一部分使用 &lt;code&gt;DeviceId&lt;/code&gt;，则可能出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同一个人被识别成多个用户&lt;/li&gt;
&lt;li&gt;不同漏斗阶段无法关联&lt;/li&gt;
&lt;li&gt;留存率被低估&lt;/li&gt;
&lt;li&gt;AU 被高估&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此最好建立统一的 &lt;code&gt;CanonicalUserId&lt;/code&gt; 或明确身份拼接规则。&lt;/p&gt;
&lt;h2 id="32-明确时间口径"&gt;3.2 明确时间口径
&lt;/h2&gt;&lt;p&gt;需要固定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 UTC 还是 Market Local Time&lt;/li&gt;
&lt;li&gt;自然日从几点到几点&lt;/li&gt;
&lt;li&gt;跨市场报表按哪个时区计算&lt;/li&gt;
&lt;li&gt;迟到数据如何回补&lt;/li&gt;
&lt;li&gt;回补后是否重算历史 AU 和留存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于跨 Market 的全球数据，同一个 UTC 时间可能属于不同地区的不同自然日。&lt;/p&gt;
&lt;h2 id="33-au-字段不能默认-sum"&gt;3.3 AU 字段不能默认 SUM
&lt;/h2&gt;&lt;p&gt;在 BI 或语义模型中，&lt;code&gt;AU&lt;/code&gt; 应标记为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不可加指标&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Non-additive Metric&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;禁止默认 &lt;code&gt;SUM&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为下面这些通常都是错误的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Plain" data-lang="Plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;月 AU = 每日 AU 求和
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;整体 AU = 各 Market AU 求和
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Partner AU = 各 Content AU 求和
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;全设备 AU = 各 Device AU 求和
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果下游经常查询固定的维度组合，可以提前生成每种 &lt;code&gt;All&lt;/code&gt; 粒度的精确 AU；如果需要大量灵活的任意维度上卷，则可以考虑用户明细、Bitmap 或近似去重结构。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="4-一句话总结"&gt;4. 一句话总结
&lt;/h1&gt;&lt;p&gt;&lt;strong&gt;Active User 的本质是“在特定时间窗口内，满足特定活跃条件的去重用户集合大小”。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多维分析&lt;/strong&gt;是在不同维度范围内分别计算这个集合。用来了解不同分群的用户活跃情况。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;漏斗分析&lt;/strong&gt;是在多个有先后关系的行为阶段之间比较用户集合。比较不同渠道的 DAU 变化，辅助运营决策。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;留存分析&lt;/strong&gt;是在不同时间点之间计算同一批用户集合的交集。基于每日 AU 计算次日/7日/30日留存率。&lt;/li&gt;
&lt;li&gt;AU 不是普通可加指标，跨日期、设备、市场、内容或渠道汇总时，通常必须重新基于用户粒度去重。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>【工作杂谈_20260806】多维分析与数据建模: CROSS JOIN 实战：灵活生成多维聚合组合</title><link>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/multidimensionalanalysis/20260806_%E5%A4%9A%E7%BB%B4%E5%88%86%E6%9E%90%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%BB%BA%E6%A8%A1_crossjoin%E5%A4%9A%E7%BB%B4%E8%81%9A%E5%90%88%E5%AE%9E%E6%88%98/</link><pubDate>Thu, 06 Aug 2026 22:17:08 +0800</pubDate><guid>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/multidimensionalanalysis/20260806_%E5%A4%9A%E7%BB%B4%E5%88%86%E6%9E%90%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%BB%BA%E6%A8%A1_crossjoin%E5%A4%9A%E7%BB%B4%E8%81%9A%E5%90%88%E5%AE%9E%E6%88%98/</guid><description>&lt;h1 id="1cross-join简介"&gt;1、CROSS JOIN简介
&lt;/h1&gt;&lt;p&gt;&lt;code&gt;CROSS JOIN&lt;/code&gt; 返回两表的笛卡尔积：左表的每一行都会与右表的每一行组合。本例两表各有 3 行，因此返回 3 \times 3 = 9 行；&lt;code&gt;NULL&lt;/code&gt; 不参与匹配判断，也不会阻止组合。&lt;/p&gt;
&lt;p&gt;连接 Hive 数据库，创建示例表并填充数据：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;16
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;17
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;18
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;19
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;20
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;21
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;22
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;23
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;24
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;25
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;create&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;table&lt;/span&gt; orders(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;user_id string,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;amount double
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;row&lt;/span&gt; format delimited fields terminated &lt;span style="color:#66d9ef"&gt;by&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;\t&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stored &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; textfile;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; orders &lt;span style="color:#66d9ef"&gt;values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(&lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;create&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;table&lt;/span&gt; users(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;user_id string,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;user_name string
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;row&lt;/span&gt; format delimited fields terminated &lt;span style="color:#66d9ef"&gt;by&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;\t&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stored &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; textfile;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; users &lt;span style="color:#66d9ef"&gt;values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;Alice&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;2&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;Bob&amp;#39;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(&lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;Charlie&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; orders;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; users;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;orders&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;user_id&lt;/th&gt;
 &lt;th&gt;amount&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;100&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NULL&lt;/td&gt;
 &lt;td&gt;200&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;300&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;users&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;user_id&lt;/th&gt;
 &lt;th&gt;user_name&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;Alice&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;Bob&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NULL&lt;/td&gt;
 &lt;td&gt;Charlie&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; orders o
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CROSS&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;JOIN&lt;/span&gt; users u;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;结果：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;user_id&lt;/th&gt;
 &lt;th&gt;amount&lt;/th&gt;
 &lt;th&gt;user_id&lt;/th&gt;
 &lt;th&gt;user_name&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;100.0&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;Alice&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;100.0&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;Bob&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;100.0&lt;/td&gt;
 &lt;td&gt;NULL&lt;/td&gt;
 &lt;td&gt;Charlie&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NULL&lt;/td&gt;
 &lt;td&gt;200.0&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;Alice&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NULL&lt;/td&gt;
 &lt;td&gt;200.0&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;Bob&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NULL&lt;/td&gt;
 &lt;td&gt;200.0&lt;/td&gt;
 &lt;td&gt;NULL&lt;/td&gt;
 &lt;td&gt;Charlie&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;300.0&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;Alice&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;300.0&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;Bob&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;300.0&lt;/td&gt;
 &lt;td&gt;NULL&lt;/td&gt;
 &lt;td&gt;Charlie&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;SQL 在没有 &lt;code&gt;ORDER BY&lt;/code&gt; 时不保证结果顺序，表格中的顺序仅用于展示。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h1 id="2大数据下的应用场景"&gt;2、大数据下的应用场景
&lt;/h1&gt;&lt;h2 id="21-引入分组聚合函数"&gt;2.1 引入分组聚合函数
&lt;/h2&gt;&lt;p&gt;假设&lt;code&gt;SourceTable&lt;/code&gt;表的记录如下，其中的&lt;code&gt;Col1, Col2,Col3,Col4, Col5&lt;/code&gt; 对应了我们关注的5个统计维度&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;PartnerID&lt;/th&gt;
 &lt;th&gt;BrandID&lt;/th&gt;
 &lt;th&gt;ContentID&lt;/th&gt;
 &lt;th&gt;Col1&lt;/th&gt;
 &lt;th&gt;Col2&lt;/th&gt;
 &lt;th&gt;Col3&lt;/th&gt;
 &lt;th&gt;Col4&lt;/th&gt;
 &lt;th&gt;Col5&lt;/th&gt;
 &lt;th&gt;UserMUID&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Video&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Sports&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Video&lt;/td&gt;
 &lt;td&gt;30&lt;/td&gt;
 &lt;td&gt;Web&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Finace&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Artical&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Finace&lt;/td&gt;
 &lt;td&gt;U2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Artical&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Sports&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如果想探究&lt;code&gt;Col1&lt;/code&gt;与用户的关系，那么就需要计算在不同的Col1值下 User的数量，也就是下面的sql&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; Col1,&lt;span style="color:#66d9ef"&gt;Count&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;distinct&lt;/span&gt; UserMUID)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; SourceTable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;group&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;by&lt;/span&gt; Col1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;同理如果想探究&lt;code&gt;Col2, cols3, col4 col5&lt;/code&gt;与用户的关系，那么就需要计算在不同的&lt;code&gt;Col2, cols3, col4 col5&lt;/code&gt;值下 User的数量，也就是下面的sql&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;14
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; Col2,&lt;span style="color:#66d9ef"&gt;Count&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;distinct&lt;/span&gt; UserMUID)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; SourceTable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;group&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;by&lt;/span&gt; Col2
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; Col3,&lt;span style="color:#66d9ef"&gt;Count&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;distinct&lt;/span&gt; UserMUID)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; SourceTable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;group&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;by&lt;/span&gt; Col3
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; Col4,&lt;span style="color:#66d9ef"&gt;Count&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;distinct&lt;/span&gt; UserMUID)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; SourceTable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;group&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;by&lt;/span&gt; Col4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;select&lt;/span&gt; Col5,&lt;span style="color:#66d9ef"&gt;Count&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;distinct&lt;/span&gt; UserMUID)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;from&lt;/span&gt; SourceTable
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;group&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;by&lt;/span&gt; Col5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;现在问题变得更复杂，如果想探究&lt;code&gt;Col1, Col2&lt;/code&gt;的组合与用户的关系，那么就需要写重新sql进行计算&lt;/p&gt;
&lt;p&gt;回想一下数学种的统计知识，已知&lt;code&gt;Col1, Col2, cols3, col4 col5&lt;/code&gt;这5个维度是互不干扰的，且每个维度的值都不为空，那么其所有的组合就是对5个位置的独立选择，这5个空位分别有两个值可选：存在/不存在。最后会得到2的5次方=32共32种组合&lt;/p&gt;
&lt;p&gt;那么如果我们想全面地分析维度与用户数量的关系，就应该得到32个类似上面的sql。很显然这种做法很蠢，是白白浪费代码和时间，属于是在时间和空间上都不讨巧的笨蛋做法。&lt;/p&gt;
&lt;p&gt;所以早在1996年就有人提出了更加优秀的解决办法：&lt;strong&gt;分组聚合函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用分组聚合函数&lt;code&gt;ROLLUP / CUBE / GROUPING SETS&lt;/code&gt;可以用一个sql直接生成上面32种维度的聚合结果指定维度的组合，&lt;/p&gt;
&lt;h3 id="211-大数据领域-olap-分组扩展引入时间线"&gt;2.1.1 大数据领域 OLAP 分组扩展引入时间线
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;table&gt;
 &lt;thead&gt;
 &lt;tr&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;1996&lt;/td&gt;
 &lt;td&gt;Gray 等学者在论文中提出 CUBE 概念&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1999–2002&lt;/td&gt;
 &lt;td&gt;SQL:1999 标准正式纳入 ROLLUP / CUBE / GROUPING SETS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;~2012–2013&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Hive 0.10.0 引入 GROUPING SETS、CUBE、ROLLUP(Hadoop 生态最早原生支持&lt;/strong&gt;​)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;2015 年中&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Spark 1.4.0 在 DataFrame API 中引入 CUBE 算子&lt;/strong&gt;​&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;2015 年底&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;Spark 1.6 补齐 rollup()，DataFrame API 多维聚合能力完整&lt;/strong&gt;​&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2016&lt;/td&gt;
 &lt;td&gt;PostgreSQL 9.5 才支持 CUBE / ROLLUP / GROUPING SETS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2017&lt;/td&gt;
 &lt;td&gt;Hive 2.3.0 对齐标准 SQL 的 GROUPING 语义&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;/blockquote&gt;
&lt;p&gt;由于这篇文章不是为了介绍分组聚合函数的，所以对三个关键字不熟悉或者感兴趣的同学可以看我这篇文章：&lt;a class="link" href="https://eleanora-lyh.github.io/MyLearningNotes/posts/hive/07hive%E5%87%BD%E6%95%B0/#%E5%85%AD%E9%AB%98%E7%BA%A7%E5%88%86%E7%BB%84%E8%81%9A%E5%90%88grouping-sets--cube--rollup" target="_blank" rel="noopener"
 &gt;HIVE高级分组聚合的 GROUPING SETS / ROLL UP / CUBE 关键字&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="22-引入cross-join搭配辅助表"&gt;2.2 引入CROSS JOIN搭配辅助表
&lt;/h2&gt;&lt;p&gt;当想一次SQL跑出多种维度组合的聚合结果时，很容易想到 &lt;a class="link" href="https://eleanora-lyh.github.io/MyLearningNotes/posts/hive/07hive%E5%87%BD%E6%95%B0/#%E5%85%AD%E9%AB%98%E7%BA%A7%E5%88%86%E7%BB%84%E8%81%9A%E5%90%88grouping-sets--cube--rollup" target="_blank" rel="noopener"
 &gt;HIVE高级分组聚合的 GROUPING SETS / ROLL UP / CUBE 关键字&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;但是有些情况还不够灵活：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果5个维度之间不存在递进关系，就不能使用&lt;code&gt;ROLL UP&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;5个维度完全组合会得到2^5次方=32共32种维度组合，但如果某些组合不想要了，就不能使用 &lt;code&gt;CUBE&lt;/code&gt;（&lt;code&gt;CUBE&lt;/code&gt;是自动按照维度计算全组合的）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;假设只需其中30种的维度组合，全部都在&lt;code&gt;GROUPING SETS&lt;/code&gt; 中一个个声明也很麻烦。而且如果维度从5个变为6个，那么代码又要重新修改。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;此时如果将维度组合显示记录在一个辅助表&lt;code&gt;DimensionCombinations&lt;/code&gt;，就可以避免上面的问题：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从数学的角度看，这5个维度的组合就是5个可重复的独立选择，这5个空位分别有两个值可选：不聚合/聚合，分别可以抽象成0/255&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt; 是一个“保留原始维度值”的控制标记。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;255&lt;/code&gt; 是一个“将该维度替换为 All”的控制标记。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那么事先将这些组合写入0/255的辅助表中，再和业务表执行 &lt;code&gt;CROSS JOIN&lt;/code&gt;就自然可以得出所有维度的组合。当想去掉某些维度的聚合时，只需要将列值置为0则不会进入聚合阶段。&lt;/p&gt;
&lt;p&gt;这里的做法我觉得很类似用空间换时间的算法，我们提前将表进行膨胀组合，就省去了后面多次按照不同维度的聚合。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;下面以完整的32个组合的辅助表&lt;code&gt;DimensionCombinations&lt;/code&gt;为例，讲下具体怎么使用&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;维度1&lt;/th&gt;
 &lt;th&gt;维度2&lt;/th&gt;
 &lt;th&gt;维度3&lt;/th&gt;
 &lt;th&gt;维度4&lt;/th&gt;
 &lt;th&gt;维度5&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&amp;hellip;&lt;/td&gt;
 &lt;td&gt;&amp;hellip;&lt;/td&gt;
 &lt;td&gt;&amp;hellip;&lt;/td&gt;
 &lt;td&gt;&amp;hellip;&lt;/td&gt;
 &lt;td&gt;&amp;hellip;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;当一条记录如下，其中的&lt;code&gt;Col1, Col2,Col3,Col4, Col5 &lt;/code&gt;对应了我们关注的5个数据维度，分别对应辅助表&lt;code&gt;DimensionCombinations&lt;/code&gt; 会进行组合的5个维度&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;PartnerID&lt;/th&gt;
 &lt;th&gt;BrandID&lt;/th&gt;
 &lt;th&gt;ContentID&lt;/th&gt;
 &lt;th&gt;Col1&lt;/th&gt;
 &lt;th&gt;Col2&lt;/th&gt;
 &lt;th&gt;Col3&lt;/th&gt;
 &lt;th&gt;Col4&lt;/th&gt;
 &lt;th&gt;Col5&lt;/th&gt;
 &lt;th&gt;UserMUID&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Video&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Sports&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这条记录和 辅助表的32 行 做 &lt;code&gt;CROSS JOIN&lt;/code&gt; 后，这一行会在逻辑上扩展成 32 行。&lt;/p&gt;
&lt;p&gt;以&lt;code&gt;Col1&lt;/code&gt; 被置为255为例，讲一下此类型的组合后续会发生什么，其他组合同理。&lt;/p&gt;
&lt;p&gt;当某条 &lt;code&gt;DimensionCombinations&lt;/code&gt; 记录的 &lt;code&gt;Col1 = 255&lt;/code&gt; 时，该组合下所有原始 &lt;code&gt;Col1&lt;/code&gt; 都被映射为统一的 &lt;code&gt;255&lt;/code&gt;，虽然 &lt;code&gt;Col1&lt;/code&gt; 仍出现在分组列中，但由于其值完全相同，效果等同于消除 &lt;code&gt;Col1&lt;/code&gt; 维度。可以理解为此组合下时分组条件从 &lt;code&gt;Col1, Col2,Col3,Col4, Col5&lt;/code&gt; 的5列变为了&lt;code&gt;Col2,Col3,Col4,Col5&lt;/code&gt; 的4列&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExtendedResult &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.PartnerID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.BrandID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col1 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col1 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col1 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col2 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col2 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col2 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col3 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col3 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col3 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col4 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col4 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col4 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col5 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col5 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col5 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserMUID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; SourceTable &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; L
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;CROSS&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;JOIN&lt;/span&gt; DimensionCombinations &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; R;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;那么此时再执行 &lt;code&gt;COUNT(DISTINCT UserMUID)&lt;/code&gt;（如下），膨胀出来的32行的具有相同的&lt;code&gt;UserMUID&lt;/code&gt;，&lt;code&gt;CROSS JOIN&lt;/code&gt;只是提前将所有统计维度提前应用到原始行上，组合结果中如果&lt;code&gt;Col1 = 255&lt;/code&gt;则表示消除了此维度，只留下一个值255&lt;/p&gt;
&lt;p&gt;中文的含义就是以&lt;code&gt;Col1=all&lt;/code&gt;维度的汇总数据&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AggResult &lt;span style="color:#f92672"&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PartnerID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BrandID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col1 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col2 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col3 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col4 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col5 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; UserMUID) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; AUCount
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; ExtendedResult;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;以此类推，这样通过一个辅助表&lt;code&gt;DimensionCombinations&lt;/code&gt;，就可以通过一次分组计算得到任意5个维度的所有组合，而不必指定具体维度名字。如果有其他表的其他列也需要进行5个维度的全组合，也可以使用同样的辅助表。&lt;/p&gt;
&lt;p&gt;以这种方式来计算多维度下的统计数据，可以减少计算资源的浪费，因为只读取了一次源数据，就得到了所有维度的统计结果，避免了重复读取。&lt;/p&gt;
&lt;h2 id="23-详细步骤"&gt;2.3 详细步骤
&lt;/h2&gt;&lt;p&gt;如果上面的使用方法的抽象概念没有看懂，可以看下这里的分步详解；如果上面的讲解能够理解，这一小节可以跳过。&lt;/p&gt;
&lt;p&gt;表还是SourceTable，以下面的记录为例&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;PartnerID&lt;/th&gt;
 &lt;th&gt;BrandID&lt;/th&gt;
 &lt;th&gt;ContentID&lt;/th&gt;
 &lt;th&gt;Col1&lt;/th&gt;
 &lt;th&gt;Col2&lt;/th&gt;
 &lt;th&gt;Col3&lt;/th&gt;
 &lt;th&gt;Col4&lt;/th&gt;
 &lt;th&gt;Col5&lt;/th&gt;
 &lt;th&gt;UserMUID&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Video&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Sports&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Video&lt;/td&gt;
 &lt;td&gt;30&lt;/td&gt;
 &lt;td&gt;Web&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Finace&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Artical&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Finace&lt;/td&gt;
 &lt;td&gt;U2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;Artical&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Sports&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;为了便于理解，暂时不看&lt;code&gt;DimensionCombinations&lt;/code&gt;的全部 32 行，只取下面两个组合：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Col1    Col2    Col3    Col4    Col5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;255     0 0 0 0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;255     255 0 0 255
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;它们分别表示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;255 0 0 0 0 = Col1 上卷为 All，其他四个维度保留原值
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;255 255 0 0 255 = Col1、Col2、Col5 上卷为 All，其他两个维度保留原值
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;代码中的表达式：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;R.Col1 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col1 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;含义是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;R.Col1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;输出&lt;/span&gt; L.Col1&lt;span style="color:#960050;background-color:#1e0010"&gt;，保留原值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;R.Col1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;输出&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;，表示&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;All&lt;/span&gt; Col1 Types
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;原始4条数据经过下面的sql转换后&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExtendedResult &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.PartnerID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.BrandID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.ContentID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col1 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col1 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col1 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col2 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col2 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col2 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col3 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col3 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col3 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col4 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col4 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col4 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col5 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col5 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col5 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserMUID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; SourceTable &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; L
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;CROSS&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;JOIN&lt;/span&gt; DimensionCombinations &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; R;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;就会膨胀得到4*32行，表示原始行与32个维度的组合&lt;/p&gt;
&lt;h3 id="231-第一个组合255-0-0-0-0"&gt;2.3.1 第一个组合：255 0 0 0 0
&lt;/h3&gt;&lt;p&gt;当组合为：&lt;code&gt;255 0 0 0 0&lt;/code&gt;，表示Col1 上卷为 All。执行下面的代码后&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExtendedResult &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.PartnerID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.BrandID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.ContentID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col1 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col1 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col1 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col2 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col2 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col2 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col3 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col3 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col3 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col4 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col4 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col4 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col5 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col5 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col5 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserMUID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; SourceTable &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; L
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;CROSS&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;JOIN&lt;/span&gt; DimensionCombinations &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; R;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;表达式会把每条记录的 Col1 都改成 255，此时Col1=all（即不再区分 Col1），第一行、第四行的数据在Col1~Col5这几列是完全一致的&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;PartnerID&lt;/th&gt;
 &lt;th&gt;BrandID&lt;/th&gt;
 &lt;th&gt;ContentID&lt;/th&gt;
 &lt;th&gt;Col1&lt;/th&gt;
 &lt;th&gt;Col2&lt;/th&gt;
 &lt;th&gt;Col3&lt;/th&gt;
 &lt;th&gt;Col4&lt;/th&gt;
 &lt;th&gt;Col5&lt;/th&gt;
 &lt;th&gt;UserMUID&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Sports&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;30&lt;/td&gt;
 &lt;td&gt;Web&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Finace&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Finace&lt;/td&gt;
 &lt;td&gt;U2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Sports&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这部分数据再进行分组&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AggResult &lt;span style="color:#f92672"&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PartnerID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BrandID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ContentID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col2,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col3,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col4,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col5,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; UserMUID) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; AUCount
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; ExtendedResult;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;统计结果如下&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;PartnerID&lt;/th&gt;
 &lt;th&gt;BrandID&lt;/th&gt;
 &lt;th&gt;ContentID&lt;/th&gt;
 &lt;th&gt;Col1&lt;/th&gt;
 &lt;th&gt;Col2&lt;/th&gt;
 &lt;th&gt;Col3&lt;/th&gt;
 &lt;th&gt;Col4&lt;/th&gt;
 &lt;th&gt;Col5&lt;/th&gt;
 &lt;th&gt;AUCount&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Sports&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;30&lt;/td&gt;
 &lt;td&gt;Web&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Finace&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;20&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;Finace&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;需要注意由于原来的第一行和第四行是同一个用户，所以ActiveUser通过DISTINCT只能算作一个&lt;/p&gt;
&lt;h3 id="232-第一个组合255-255-0-0-255"&gt;2.3.2 第一个组合：255 255 0 0 255
&lt;/h3&gt;&lt;p&gt;当组合为：&lt;code&gt;255 255 0 0 255&lt;/code&gt;，表示Col1,Col2,Col5 上卷为 All。执行下面的代码后&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExtendedResult &lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.PartnerID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.BrandID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; L.ContentID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col1 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col1 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col1 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col2 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col2 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col2 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col3 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col3 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col3 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col4 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col4 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col4 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; R.Col5 &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;?&lt;/span&gt; L.Col5 : &lt;span style="color:#ae81ff"&gt;255&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; Col5 ,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; UserMUID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; SourceTable &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; L
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;CROSS&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;JOIN&lt;/span&gt; DimensionCombinations &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; R;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;表达式会把每条记录的 Col1,Col2,Col5 的值都改成 255，此时Col1=all, Col2=all, Col5=all,（即不再区分 Col1,Col2,Col5），第一行、第三行、第四行的数据在Col1~Col5这几列是完全一致的&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;PartnerID&lt;/th&gt;
 &lt;th&gt;BrandID&lt;/th&gt;
 &lt;th&gt;ContentID&lt;/th&gt;
 &lt;th&gt;Col1&lt;/th&gt;
 &lt;th&gt;Col2&lt;/th&gt;
 &lt;th&gt;Col3&lt;/th&gt;
 &lt;th&gt;Col4&lt;/th&gt;
 &lt;th&gt;Col5&lt;/th&gt;
 &lt;th&gt;UserMUID&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;Web&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;U2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;U1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这部分数据再进行分组&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt; 9
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;10
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;11
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AggResult &lt;span style="color:#f92672"&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PartnerID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BrandID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ContentID,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col1,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col2,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col3,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col4,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Col5,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;DISTINCT&lt;/span&gt; UserMUID) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; AUCount
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; ExtendedResult;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;统计结果如下&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;PartnerID&lt;/th&gt;
 &lt;th&gt;BrandID&lt;/th&gt;
 &lt;th&gt;ContentID&lt;/th&gt;
 &lt;th&gt;Col1&lt;/th&gt;
 &lt;th&gt;Col2&lt;/th&gt;
 &lt;th&gt;Col3&lt;/th&gt;
 &lt;th&gt;Col4&lt;/th&gt;
 &lt;th&gt;Col5&lt;/th&gt;
 &lt;th&gt;AUCount&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;App&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;P1&lt;/td&gt;
 &lt;td&gt;B1&lt;/td&gt;
 &lt;td&gt;C1&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;Web&lt;/td&gt;
 &lt;td&gt;CN&lt;/td&gt;
 &lt;td&gt;255&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="233-组合总结"&gt;2.3.3 组合总结
&lt;/h3&gt;&lt;p&gt;通过上面的两种组合的案例，应该可以很好地将这个逻辑扩展到剩余的组合中。&lt;/p&gt;
&lt;p&gt;当初始表的维度为5个时，光统计一个AUCount（ActiveUserCount）指标我们就可以膨胀出2^5=32倍的记录。也就是说随着表的维度增加，统计的指标增加，最终膨胀出的行数是以指数级别扩张的。（当数据量达到千万以上时需要注意数据倾斜的问题）&lt;/p&gt;
&lt;p&gt;所以这时候使用CROSS JOIN辅助表的优势就会越来越明显，因为一次&lt;code&gt;CROSS JOIN&lt;/code&gt;可以得出多个维度组合的统计，不仅比分开写多条GROUP BY的代码更简洁，还减少对原始表的重复扫描，并减少了Reduce的次数。&lt;/p&gt;
&lt;h1 id="3cross-join扩展维度的优势"&gt;3、CROSS JOIN扩展维度的优势
&lt;/h1&gt;&lt;p&gt;最后再总结一下使用CORSS JOIN 辅助表的好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不依赖特定 &lt;code&gt;CUBE&lt;/code&gt; 语法，可通过修改资源文件增加或删除某些组合。&lt;/li&gt;
&lt;li&gt;可以统一用 &lt;code&gt;255&lt;/code&gt; 表示 &lt;code&gt;All&lt;/code&gt;，避免用 &lt;code&gt;NULL&lt;/code&gt; 与真实空值混淆。&lt;/li&gt;
&lt;li&gt;其他表的处理可以复用相近的维度组合逻辑。&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>【工作杂谈_20260731】数据平台与工程治理: MySQL 时区转换：UTC、北京时间与 PST/PDT 的对齐</title><link>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/dataplatformengineering/20260731_%E6%95%B0%E6%8D%AE%E5%B9%B3%E5%8F%B0%E4%B8%8E%E5%B7%A5%E7%A8%8B%E6%B2%BB%E7%90%86_mysql%E6%97%B6%E5%8C%BA%E8%BD%AC%E6%8D%A2/</link><pubDate>Fri, 31 Jul 2026 18:18:00 +0800</pubDate><guid>https://eleanora-lyh.github.io/MyLearningNotes/posts/work/dataplatformengineering/20260731_%E6%95%B0%E6%8D%AE%E5%B9%B3%E5%8F%B0%E4%B8%8E%E5%B7%A5%E7%A8%8B%E6%B2%BB%E7%90%86_mysql%E6%97%B6%E5%8C%BA%E8%BD%AC%E6%8D%A2/</guid><description>&lt;h1 id="1问题引入"&gt;1、问题引入
&lt;/h1&gt;&lt;p&gt;先介绍一下上游数据：L1事件明细，记录了&lt;strong&gt;Web 侧事件级遥测数据，包含用户行为及部分系统/测试/机器人事件&lt;/strong&gt;。此文件每一个小时记录会生成一个，每个大小为5-30TB左右。&lt;/p&gt;
&lt;p&gt;假设当前时间为curr_time，我们需要得到&lt;code&gt;curr_time-2h, curr_time-1h, curr_time, curr_time+1h&lt;/code&gt;共计四个文件才能开始当前时间为curr_time的计算任务。每两个小时开始一个这样的计算任务，也就是说一天需要完成12个并生成对应12个文件，每个计算任务完成都会在数据库插入一条记录。&lt;/p&gt;
&lt;p&gt;目前的保障措施：因为上游数据数据达到的时间不确定，所以如果想保证计算任务顺利执行，在设计ETL流程时会根据当前时间预判可能的到达时间，并wait10h，在此期间如果需要的所有上游文件都到达就会触发计算任务。同时增加了对上游数据的监控，在规定时间范围内未到达就会触发通知。&lt;/p&gt;
&lt;p&gt;但这样做还是有一个遗漏之处，就是是上游文件在规定时间范围内到达，但是由于计算任务时间过长或者计算量太大，就会导致计算任务无法准时完成。而目前监控计算任务是否完成只能通过肉眼去查文件是否生成。&lt;/p&gt;
&lt;p&gt;因此引入我的任务：在grafana中新建一个dashboard来可视化计算任务的结果，用来判断计算任务是否完成
实现过程也很简单：去查对应时间的数据库内是否有计算任务完成的记录，构造本应在一天中生成的12个文件行，将其与数据库进行Join，对的上的标记为存在，对不上的标记为不存在。&lt;/p&gt;
&lt;p&gt;在实现的过程中我发现自己踩了时区转化的坑，导致dashboard中展示的计算结果的create_time和实际create_time不同，具体踩坑点如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;grafana的dashboard中有时间范围筛选，其默认时区为北京时间&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mysql数据库的时区为UTC时间&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;计算任务生成的结果存储在Azure云上，其时区为PDT时间&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样就导致了同一条记录在不同的地方查询显示/操作的create_time都是不同的，所以此文章介绍一下时区转换&lt;/p&gt;
&lt;h1 id="2时间解析"&gt;2、时间解析
&lt;/h1&gt;&lt;h2 id="21-什么是-utc-时间"&gt;2.1 什么是 UTC 时间
&lt;/h2&gt;&lt;h3 id="211-utc-是全球统一的时间基准"&gt;2.1.1 UTC 是全球统一的时间基准
&lt;/h3&gt;&lt;p&gt;UTC 全称：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;Coordinated Universal Time，协调世界时&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;它不是某个国家或城市的地方时间，而是全球系统进行时间同步时使用的基准。&lt;/p&gt;
&lt;p&gt;常见表示方式：&lt;code&gt;2026-07-31T06:30:00Z&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2026-07-31&lt;/code&gt;：日期&lt;/li&gt;
&lt;li&gt;&lt;code&gt;T&lt;/code&gt;：日期和时间的分隔符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;06:30:00&lt;/code&gt;：时、分、秒&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Z&lt;/code&gt;：表示 UTC，也叫 Zulu Time&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="212-utc-和-gmt-的关系"&gt;2.1.2 UTC 和 GMT 的关系
&lt;/h3&gt;&lt;p&gt;日常业务中，&lt;code&gt;UTC+0&lt;/code&gt;和&lt;code&gt;GMT+0&lt;/code&gt;两者通常可以视为相同偏移：&lt;/p&gt;
&lt;p&gt;但概念上并不完全相同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UTC&lt;/strong&gt;：Coordinated Universal Time，现代时间标准，基于原子钟等时间体系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GMT&lt;/strong&gt;： Greenwich Mean Time，格林尼治平均时，最初基于天文观测，也常被用作时区名称。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于日志、数据库、API、Pipeline 和告警系统，建议统一使用 &lt;strong&gt;UTC&lt;/strong&gt;，而不是 GMT。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="22-什么是北京时间cst"&gt;2.2 什么是北京时间CST
&lt;/h2&gt;&lt;p&gt;北京时间CST，通常指：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;China Standard Time（CST）&lt;/strong&gt; ，常表示为 UTC+08:00&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;换算关系：&lt;/p&gt;
&lt;p&gt;UTC： 2026-07-31 16:30，对应北京时间： 2026-08-01 00:30（注意，减去或加上 8 小时后，&lt;strong&gt;日期也可能变化&lt;/strong&gt;）&lt;/p&gt;
&lt;h3 id="221-北京时间没有夏令时切换"&gt;2.2.1 北京时间没有夏令时切换
&lt;/h3&gt;&lt;p&gt;目前中国标准时间全年固定为：&lt;strong&gt;UTC+8&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此，在当前规则下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;夏天是 UTC+8&lt;/li&gt;
&lt;li&gt;冬天也是 UTC+8&lt;/li&gt;
&lt;li&gt;不存在北京时间突然跳过一小时或重复一小时的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不过，跨时区项目中不要把它简单写成模糊的 &lt;code&gt;CST&lt;/code&gt;。因为 &lt;code&gt;CST&lt;/code&gt; 可能被解释为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;China Standard Time&lt;/li&gt;
&lt;li&gt;Central Standard Time&lt;/li&gt;
&lt;li&gt;Cuba Standard Time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更明确的写法是：&lt;code&gt;UTC+08:00&lt;/code&gt;和 &lt;code&gt;Asia/Shanghai&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="23-什么是-pst-和-pdt"&gt;2.3 什么是 PST 和 PDT
&lt;/h2&gt;&lt;p&gt;PST 和 PDT 都与北美太平洋时区有关，他们是北美太平洋时区在不同月份范围的细分时区，但是可以统称为Pacific Time（缩写为PT），即&lt;strong&gt;PT = PST 或 PDT&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;按美国现行的一般规则，夏令时从每年 3 月第二个星期日开始，到 11 月第一个星期日结束。这个时间范围内采用PDT时区，剩余时间范围采用PST时区。&lt;/p&gt;
&lt;h3 id="231-pstpacific-standard-time"&gt;2.3.1 PST：Pacific Standard Time
&lt;/h3&gt;&lt;p&gt;太平洋标准时间 Pacific Standard Time （缩写PST），主要对应不使用夏令时(DST)的时期&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;UTC 偏移：UTC-8&lt;/strong&gt;（比 UTC 慢 8 小时）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用地区&lt;/strong&gt;：美国西海岸（加州、华盛顿州、俄勒冈州、内华达州等）、加拿大不列颠哥伦比亚省，主要城市包括洛杉矶、旧金山、西雅图、波特兰、温哥华&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;启用时段&lt;/strong&gt;：每年 &lt;strong&gt;11 月第一个星期日&lt;/strong&gt;到次年 &lt;strong&gt;3 月第二个星期日&lt;/strong&gt;（即冬令时）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;夏令时切换&lt;/strong&gt;：3 月第二个星期日起切换为 &lt;strong&gt;PDT（UTC-7）&lt;/strong&gt;，11 月第一个星期日回切到 PST&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="232-pdtpacific-daylight-time"&gt;2.3.2 PDT：Pacific Daylight Time
&lt;/h3&gt;&lt;p&gt;太平洋夏令时间 Pacific Daylight Time（PDT） 在实行夏令时期间使用&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;UTC 偏移：PDT = UTC-7&lt;/strong&gt;（比 UTC 晚 7 小时）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用地区&lt;/strong&gt;：美国西海岸（加州、华盛顿州、内华达州、俄勒冈州等）、加拿大不列颠哥伦比亚省，主要城市包括洛杉矶、旧金山、西雅图、波特兰、温哥华等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;启用时段&lt;/strong&gt;：每年 &lt;strong&gt;3 月第二个星期日&lt;/strong&gt;到 &lt;strong&gt;11 月第一个星期日&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;冬令时切换&lt;/strong&gt;：夏令时结束后切换回 &lt;strong&gt;PST（Pacific Standard Time，太平洋标准时间，UTC-8）&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;💡 注意：PDT 只在夏令时期间有效（3 月中～11 月初）。其余月份美国西海岸用的是 PST（UTC-8）。如果你的业务时间跨度覆盖全年，&lt;strong&gt;不能简单地固定减 7 小时&lt;/strong&gt;，应该用命名时区让 MySQL 自动处理切换：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CONVERT_TZ(utc_time, &lt;span style="color:#e6db74"&gt;&amp;#39;+00:00&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;America/Los_Angeles&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="233-pst-与-pdt-的关系"&gt;2.3.3 PST 与 PDT 的关系
&lt;/h3&gt;&lt;p&gt;两者指的是&lt;strong&gt;同一个地理区域的同一条时间线&lt;/strong&gt;，只是根据季节在&amp;quot;标准时间&amp;quot;和&amp;quot;夏令时&amp;quot;之间切换：&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;UTC 偏移&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;冬季（11 月～3 月）&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;PST&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;Pacific &lt;strong&gt;Standard&lt;/strong&gt;​ Time&lt;/td&gt;
 &lt;td&gt;UTC-8&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;夏季（3 月～11 月）&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;PDT&lt;/strong&gt;​&lt;/td&gt;
 &lt;td&gt;Pacific &lt;strong&gt;Daylight&lt;/strong&gt;​ Time&lt;/td&gt;
 &lt;td&gt;UTC-7&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;也就是说，&lt;strong&gt;PDT = PST + 1 小时&lt;/strong&gt;。夏令时开始时时钟拨快 1 小时（PST → PDT），结束时拨慢 1 小时（PDT → PST）。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;💡 所以严格来说，&amp;ldquo;Pacific Time（PT）&amp;ldquo;是一个统称，它底下分 PST 和 PDT 两种状态，随季节切换。 &lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="24-pstpdt-与北京时间相差多少"&gt;2.4 PST/PDT 与北京时间相差多少
&lt;/h2&gt;&lt;p&gt;已知：&lt;/p&gt;
&lt;p&gt;PDT = UTC-7，PST = UTC-8，北京时间 = UTC+8&lt;/p&gt;
&lt;p&gt;所以可以得出：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;太平洋当地时间&lt;/th&gt;
 &lt;th&gt;UTC 偏移&lt;/th&gt;
 &lt;th&gt;与北京时间的时差&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;PST&lt;/td&gt;
 &lt;td&gt;UTC-8&lt;/td&gt;
 &lt;td&gt;CST=PST+16H&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PDT&lt;/td&gt;
 &lt;td&gt;UTC-7&lt;/td&gt;
 &lt;td&gt;CST=PST+15H&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h1 id="3跨时区项目一般怎么处理"&gt;3、跨时区项目一般怎么处理
&lt;/h1&gt;&lt;h2 id="原则一后端和数据层统一使用-utc"&gt;原则一：后端和数据层统一使用 UTC
&lt;/h2&gt;&lt;p&gt;建议以下内容全部保存为 UTC：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据库时间字段&lt;/li&gt;
&lt;li&gt;Pipeline 的运行时间&lt;/li&gt;
&lt;li&gt;文件到达时间&lt;/li&gt;
&lt;li&gt;日志时间&lt;/li&gt;
&lt;li&gt;API 请求时间&lt;/li&gt;
&lt;li&gt;事件发生时间&lt;/li&gt;
&lt;li&gt;告警检测时间&lt;/li&gt;
&lt;li&gt;消息队列中的时间&lt;/li&gt;
&lt;li&gt;分布式系统 Watermark&lt;/li&gt;
&lt;li&gt;审计记录&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;例如：&lt;code&gt;2026-07-31T06:30:00Z&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;而不是只保存：&lt;code&gt;2026-07-31 14:30:00&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;后者没有说明时区，无法判断它是北京时间、UTC 还是美国当地时间。&lt;/p&gt;
&lt;h2 id="原则二展示时才转成用户当地时间"&gt;原则二：展示时才转成用户当地时间
&lt;/h2&gt;&lt;p&gt;典型流程是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;5
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;6
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;7
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;8
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;事件发生
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;转换为 UTC
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;数据库按 UTC 保存
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;API 返回 UTC
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;↓
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;前端根据用户时区显示
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h1 id="4我的项目怎么处理"&gt;4、我的项目怎么处理
&lt;/h1&gt;&lt;p&gt;因为想的是替换掉手动去云存储那里一个个查文件的过程，所以dashboard这里显示的时间要和云存储的create_time一致&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MySQL create_time = UTC
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;云存储 create_time = 洛杉矶当地时间
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;针对目标查询表加一层view视图，将转为洛杉矶当地时间，如下&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CONVERT_TZ(create_time, &lt;span style="color:#e6db74"&gt;&amp;#39;+00:00&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;America/Los_Angeles&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;如果转换完得到的时间为NULL，是因为MySQL 必须查询内部时区表，以确定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;America/Los_Angeles&lt;/code&gt; 是否存在&lt;/li&gt;
&lt;li&gt;2026 年 7 月 31 日是否处于夏令时&lt;/li&gt;
&lt;li&gt;此时应该使用 UTC-7 还是 UTC-8&lt;/li&gt;
&lt;li&gt;夏令时切换边界如何处理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当前 MySQL 查不到对应的时区规则，因此返回 &lt;code&gt;NULL&lt;/code&gt;。解决办法详见下面的4.1、4.2、4.3&lt;/p&gt;
&lt;h3 id="41-查看mysql-server-和-session-当前使用的时区"&gt;4.1 查看MySQL Server 和 Session 当前使用的时区
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;@@&lt;/span&gt;system_time_zone &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; system_time_zone,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;@@&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;global&lt;/span&gt;.time_zone &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; global_time_zone,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;@@&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;session&lt;/span&gt;.time_zone &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; session_time_zone;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;返回&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;system_time_zone&lt;/th&gt;
 &lt;th&gt;global_time_zone&lt;/th&gt;
 &lt;th&gt;session_time_zone&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;China Standard Time&lt;/td&gt;
 &lt;td&gt;SYSTEM&lt;/td&gt;
 &lt;td&gt;SYSTEM&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="42-查看命名时区表是否已经加载"&gt;4.2 查看命名时区表是否已经加载
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;COUNT&lt;/span&gt;(&lt;span style="color:#f92672"&gt;*&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; timezone_count
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; mysql.time_zone_name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WHERE&lt;/span&gt; Name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;America/Los_Angeles&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;返回&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;timezone_count&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="43-开启时区数据库"&gt;4.3 开启时区数据库
&lt;/h2&gt;&lt;p&gt;要在 MySQL 中使用&lt;strong&gt;命名时区&lt;/strong&gt;（如 &lt;code&gt;'America/Los_Angeles'&lt;/code&gt;、&lt;code&gt;'Asia/Shanghai'&lt;/code&gt;），必须先把系统的 &lt;strong&gt;IANA 时区数据库&lt;/strong&gt;（zoneinfo）加载进 MySQL 自带的 &lt;code&gt;mysql.time_zone_*&lt;/code&gt; 系统表里。否则 &lt;code&gt;CONVERT_TZ&lt;/code&gt; 用命名时区时会返回 &lt;code&gt;NULL&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id="431-windows系统"&gt;4.3.1 windows系统
&lt;/h3&gt;&lt;p&gt;windows系统则首先下载 MySQL 时区包：&lt;a class="link" href="https://dev.mysql.com/downloads/timezones.html" target="_blank" rel="noopener"
 &gt;MySQL :: Time zone description tables&lt;/a&gt;，选择&lt;strong&gt;timezone_2026c_posix_sql.zip&lt;/strong&gt;，解压到对应位置（我的位置是：C:\Users\v-liuyuhang\AppData\Roaming\DBeaverData\workspace6\General\timezone_posix.sql）
打开cmd，进入mysql.exe所在目录，我的是 C:\Program Files\MySQL\MySQL Server 8.4\bin&lt;/p&gt;
&lt;p&gt;执行命令：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql -u root -p mysql &amp;lt; C:&lt;span style="color:#ae81ff"&gt;\U&lt;/span&gt;sers&lt;span style="color:#ae81ff"&gt;\v&lt;/span&gt;-liuyuhang&lt;span style="color:#ae81ff"&gt;\A&lt;/span&gt;ppData&lt;span style="color:#ae81ff"&gt;\R&lt;/span&gt;oaming&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;BeaverData&lt;span style="color:#ae81ff"&gt;\w&lt;/span&gt;orkspace6&lt;span style="color:#ae81ff"&gt;\G&lt;/span&gt;eneral&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;imezone_posix.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="432-linux系统"&gt;4.3.2 Linux系统
&lt;/h3&gt;&lt;p&gt;大多数 Linux 发行版和 macOS 在 /usr/share/zoneinfo 下都有 IANA 数据库，直接用一行管道加载&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="434-云数据库"&gt;4.3.4 云数据库
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mysql --host&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;xxx.com&amp;#34;&lt;/span&gt; --port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3306&lt;/span&gt; --user&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;xxx&amp;#34;&lt;/span&gt; --password --ssl-mode&lt;span style="color:#f92672"&gt;=&lt;/span&gt;REQUIRED mysql &amp;lt; C:&lt;span style="color:#ae81ff"&gt;\U&lt;/span&gt;sers&lt;span style="color:#ae81ff"&gt;\v&lt;/span&gt;-liuyuhang&lt;span style="color:#ae81ff"&gt;\A&lt;/span&gt;ppData&lt;span style="color:#ae81ff"&gt;\R&lt;/span&gt;oaming&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;BeaverData&lt;span style="color:#ae81ff"&gt;\w&lt;/span&gt;orkspace6&lt;span style="color:#ae81ff"&gt;\G&lt;/span&gt;eneral&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;imezone_posix.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;然后弹出 Enter password: 输入密码&lt;/p&gt;
&lt;h3 id="433-验证"&gt;4.3.3 验证
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;1
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;2
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;3
&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;SELECT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NOW() &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; mysql_original_time,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CONVERT_TZ( NOW() , &lt;span style="color:#e6db74"&gt;&amp;#39;+00:00&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;America/Los_Angeles&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; los_angeles_time,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CONVERT_TZ( NOW() , &lt;span style="color:#e6db74"&gt;&amp;#39;+00:00&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;+08:00&amp;#39;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; beijing_time;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;返回&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;mysql_original_time&lt;/th&gt;
 &lt;th&gt;los_angeles_time&lt;/th&gt;
 &lt;th&gt;beijing_time&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-07-31 17:36:02&lt;/td&gt;
 &lt;td&gt;2026-07-31 10:36:02&lt;/td&gt;
 &lt;td&gt;2026-08-01 01:36:02&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;</description></item></channel></rss>