<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>主键 on 牛哥聊技术</title><link>https://www.lingcoder.com/tags/%E4%B8%BB%E9%94%AE/</link><description>Recent content in 主键 on 牛哥聊技术</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Tue, 25 Nov 2025 15:30:00 +0800</lastBuildDate><atom:link href="https://www.lingcoder.com/tags/%E4%B8%BB%E9%94%AE/index.xml" rel="self" type="application/rss+xml"/><item><title>逻辑主键还是业务主键？数据库主键设计的取舍与最佳实践</title><link>https://www.lingcoder.com/p/logical-vs-business-primary-key/</link><pubDate>Tue, 25 Nov 2025 15:30:00 +0800</pubDate><guid>https://www.lingcoder.com/p/logical-vs-business-primary-key/</guid><description>&lt;img src="https://www.lingcoder.com/p/logical-vs-business-primary-key/cover.svg" alt="Featured image of post 逻辑主键还是业务主键？数据库主键设计的取舍与最佳实践" /&gt;&lt;h2 id="写在前面"&gt;&lt;a href="#%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2" class="header-anchor"&gt;&lt;/a&gt;写在前面
&lt;/h2&gt;&lt;p&gt;数据库主键看似简单——但选错了能让一个表&amp;quot;永远改不动&amp;quot;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务编码当主键 → 业务规则一变全表 rebuild&lt;/li&gt;
&lt;li&gt;UUID 当主键 → 索引性能差、空间膨胀&lt;/li&gt;
&lt;li&gt;自增 ID → 跨库迁移血泪&lt;/li&gt;
&lt;li&gt;雪花 ID → 时钟回拨能让你怀疑人生&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;新人常困惑——&lt;strong&gt;到底该选哪个&lt;/strong&gt;？本文讲清五种主流方案（自增 BIGINT、UUID v4、UUID v7、雪花 ID、业务键作主键）的优劣、取舍、典型场景。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一概念先分清逻辑主键-vs-业务主键"&gt;&lt;a href="#%e4%b8%80%e6%a6%82%e5%bf%b5%e5%85%88%e5%88%86%e6%b8%85%e9%80%bb%e8%be%91%e4%b8%bb%e9%94%ae-vs-%e4%b8%9a%e5%8a%a1%e4%b8%bb%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;一、概念先分清：逻辑主键 vs 业务主键
&lt;/h2&gt;&lt;h3 id="逻辑主键"&gt;&lt;a href="#%e9%80%bb%e8%be%91%e4%b8%bb%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;逻辑主键
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;和业务无关的&amp;quot;内部标识&amp;quot;&lt;/strong&gt;——纯技术存在。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自增 BIGINT&lt;/li&gt;
&lt;li&gt;UUID&lt;/li&gt;
&lt;li&gt;雪花 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特点：&lt;strong&gt;业务永远改不到它&lt;/strong&gt;，因为它没有业务含义。&lt;/p&gt;
&lt;h3 id="业务主键"&gt;&lt;a href="#%e4%b8%9a%e5%8a%a1%e4%b8%bb%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;业务主键
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;业务上有意义的字段&lt;/strong&gt;——天然唯一。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户表用 &lt;code&gt;username&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;订单表用 &lt;code&gt;order_no&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;商品表用 &lt;code&gt;sku_code&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特点：业务可能要求改格式、改前缀、改长度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这两个概念是冲突的&lt;/strong&gt;——主键应该用哪一种？答案不是非黑即白。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二五种主流方案"&gt;&lt;a href="#%e4%ba%8c%e4%ba%94%e7%a7%8d%e4%b8%bb%e6%b5%81%e6%96%b9%e6%a1%88" class="header-anchor"&gt;&lt;/a&gt;二、五种主流方案
&lt;/h2&gt;&lt;h3 id="方案-1自增-bigint-逻辑主键推荐默认"&gt;&lt;a href="#%e6%96%b9%e6%a1%88-1%e8%87%aa%e5%a2%9e-bigint-%e9%80%bb%e8%be%91%e4%b8%bb%e9%94%ae%e6%8e%a8%e8%8d%90%e9%bb%98%e8%ae%a4" class="header-anchor"&gt;&lt;/a&gt;方案 1：自增 BIGINT 逻辑主键（推荐默认）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/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;ul&gt;
&lt;li&gt;&lt;strong&gt;写入性能极好&lt;/strong&gt;——InnoDB 按主键聚簇，递增 ID 永远 append&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;索引体积小&lt;/strong&gt;——8 字节&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JOIN 高效&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;strong&gt;跨库迁移痛&lt;/strong&gt;——两库的自增 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;code&gt;id=1234567&lt;/code&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;strong&gt;80% 的业务表用这个&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="方案-2uuid"&gt;&lt;a href="#%e6%96%b9%e6%a1%88-2uuid" class="header-anchor"&gt;&lt;/a&gt;方案 2：UUID
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BINARY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/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;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;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;写入性能差&lt;/strong&gt;——UUID 无序，每次插入都可能引发页分裂&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;空间大&lt;/strong&gt;——16 字节，char(36) 文本形式更是 36 字节&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;索引差&lt;/strong&gt;——B+ Tree 上散列严重&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JOIN 慢&lt;/strong&gt;——字符串比较开销大&lt;/li&gt;
&lt;/ul&gt;

 &lt;blockquote&gt;
 &lt;p&gt;用 UUID 一定要 &lt;code&gt;BINARY(16)&lt;/code&gt; 不能 &lt;code&gt;CHAR(36)&lt;/code&gt;——16 字节 vs 36 字符，索引和 JOIN 性能差距巨大。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;适用&lt;/strong&gt;：&lt;strong&gt;离线生成 ID 的场景&lt;/strong&gt;（移动端先生成、再上传）、对中心化发号有强烈反感的场景。&lt;strong&gt;多数 Web 业务不推荐&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="方案-3uuid-v7时间有序-uuid"&gt;&lt;a href="#%e6%96%b9%e6%a1%88-3uuid-v7%e6%97%b6%e9%97%b4%e6%9c%89%e5%ba%8f-uuid" class="header-anchor"&gt;&lt;/a&gt;方案 3：UUID v7（时间有序 UUID）
&lt;/h3&gt;&lt;p&gt;UUID v7（2024 RFC 9562 标准）解决了 UUID 的写入性能问题——&lt;strong&gt;前 48 位是毫秒时间戳&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;018D2DA1-7000-7XXX-XXXX-XXXXXXXXXXXX
&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;ul&gt;
&lt;li&gt;时间递增——索引友好&lt;/li&gt;
&lt;li&gt;全球唯一——分布式无冲突&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;仍然 16 字节，比 BIGINT 大&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;会暴露生成时间（精确到毫秒）&lt;/strong&gt;——和雪花 ID 一样，敏感场景需评估&lt;/li&gt;
&lt;li&gt;工具/框架支持还在普及中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;这是 2024 后比 v4 更值得用的 UUID 形式&lt;/strong&gt;——多数语言库已经支持。&lt;/p&gt;
&lt;h3 id="方案-4雪花-idsnowflake"&gt;&lt;a href="#%e6%96%b9%e6%a1%88-4%e9%9b%aa%e8%8a%b1-idsnowflake" class="header-anchor"&gt;&lt;/a&gt;方案 4：雪花 ID（Snowflake）
&lt;/h3&gt;&lt;p&gt;Twitter 设计的分布式 ID 算法——64 位结构：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 1 bit | 41 bits | 10 bits | 12 bits |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 符号 | 时间戳（毫秒） | 工作机器 ID（5 bit dc + 5 bit machine） | 序列号 |
&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;10 bit 机器位 Twitter 原版就拆成 5 bit datacenter + 5 bit worker，最多支持 32 个数据中心 × 32 台机器 = 1024 节点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;64 位 BIGINT——和自增 ID 一样紧凑&lt;/li&gt;
&lt;li&gt;时间递增——索引友好&lt;/li&gt;
&lt;li&gt;分布式无中心&lt;/li&gt;
&lt;li&gt;高吞吐（每节点每毫秒 4096 个）&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;strong&gt;时钟回拨问题&lt;/strong&gt;——服务器时钟跳回会重复 ID&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;机器 ID 分配&lt;/strong&gt;——上千节点时管理麻烦&lt;/li&gt;
&lt;li&gt;还是暴露业务速率（每毫秒能生成多少）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;适用&lt;/strong&gt;：&lt;strong&gt;分布式系统的标准选择&lt;/strong&gt;。美团 Leaf、百度 UidGenerator 都是改进版。&lt;/p&gt;
&lt;h3 id="方案-5业务键作主键不推荐"&gt;&lt;a href="#%e6%96%b9%e6%a1%88-5%e4%b8%9a%e5%8a%a1%e9%94%ae%e4%bd%9c%e4%b8%bb%e9%94%ae%e4%b8%8d%e6%8e%a8%e8%8d%90" class="header-anchor"&gt;&lt;/a&gt;方案 5：业务键作主键（不推荐）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/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;ul&gt;
&lt;li&gt;表自描述&lt;/li&gt;
&lt;li&gt;少一个字段&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;业务规则一变就完蛋&lt;/strong&gt;（比如 username 长度要扩展）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VARCHAR 主键聚簇索引大、慢&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关联表的外键也要 VARCHAR&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;多数 OLTP 场景都不推荐这么做&lt;/strong&gt;——除非表很小且业务键真正稳定不变。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三决策树"&gt;&lt;a href="#%e4%b8%89%e5%86%b3%e7%ad%96%e6%a0%91" class="header-anchor"&gt;&lt;/a&gt;三、决策树
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 Start([单库还是分布式?])
 Start --&gt;|单库| Small{规模小?}
 Start --&gt;|分布式| Snow[雪花 ID 或 UUID v7]
 Small --&gt;|是| Auto[自增 BIGINT]
 Small --&gt;|否，将来要分库| Snow&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;90% 的场景按这个流程决定&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;小型系统、单库&lt;/strong&gt; → 自增 BIGINT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可能分库或一开始就分布式&lt;/strong&gt; → 雪花 ID&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;离线生成 / 强分布式无中心&lt;/strong&gt; → UUID v7&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="四生产级雪花-id-的实现要点"&gt;&lt;a href="#%e5%9b%9b%e7%94%9f%e4%ba%a7%e7%ba%a7%e9%9b%aa%e8%8a%b1-id-%e7%9a%84%e5%ae%9e%e7%8e%b0%e8%a6%81%e7%82%b9" class="header-anchor"&gt;&lt;/a&gt;四、生产级雪花 ID 的实现要点
&lt;/h2&gt;&lt;p&gt;朴素雪花算法在生产里有不少坑——成熟方案要解决：&lt;/p&gt;
&lt;h3 id="1-时钟回拨"&gt;&lt;a href="#1-%e6%97%b6%e9%92%9f%e5%9b%9e%e6%8b%a8" class="header-anchor"&gt;&lt;/a&gt;1. 时钟回拨
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;绝对禁止时钟跳回&lt;/strong&gt;——可能导致 ID 重复。处理方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;启动时检查时钟，跳了就拒绝启动&lt;/li&gt;
&lt;li&gt;运行时检测，跳了就&lt;strong&gt;等回到原点再发号&lt;/strong&gt;（短暂阻塞）&lt;/li&gt;
&lt;li&gt;改进版用 &lt;code&gt;bit&lt;/code&gt; 当回拨标志位&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-机器-id-分配"&gt;&lt;a href="#2-%e6%9c%ba%e5%99%a8-id-%e5%88%86%e9%85%8d" class="header-anchor"&gt;&lt;/a&gt;2. 机器 ID 分配
&lt;/h3&gt;&lt;p&gt;朴素方案要在配置文件里写死 &lt;code&gt;worker_id&lt;/code&gt;——上千节点时是地狱。生产方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ZooKeeper / etcd 自动分配&lt;/li&gt;
&lt;li&gt;进程启动时主动注册&lt;/li&gt;
&lt;li&gt;退出时主动释放&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-数据中心-id"&gt;&lt;a href="#3-%e6%95%b0%e6%8d%ae%e4%b8%ad%e5%bf%83-id" class="header-anchor"&gt;&lt;/a&gt;3. 数据中心 ID
&lt;/h3&gt;&lt;p&gt;如上所述 Twitter 原版就把 10 bit 拆成 5+5；如果你的部署只有一个 DC，可以把 datacenter 位让渡给 worker 位，扩展到 1024 台单
DC 节点。&lt;/p&gt;
&lt;h3 id="4-序列号回滚"&gt;&lt;a href="#4-%e5%ba%8f%e5%88%97%e5%8f%b7%e5%9b%9e%e6%bb%9a" class="header-anchor"&gt;&lt;/a&gt;4. 序列号回滚
&lt;/h3&gt;&lt;p&gt;朴素方案每毫秒序列号从 0 开始——&lt;strong&gt;容易在毫秒边界 burst 时浪费&lt;/strong&gt;。改进：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上一毫秒的序列号末位作为下一毫秒的起点（散列写入）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="5-实战推荐"&gt;&lt;a href="#5-%e5%ae%9e%e6%88%98%e6%8e%a8%e8%8d%90" class="header-anchor"&gt;&lt;/a&gt;5. 实战推荐
&lt;/h3&gt;&lt;p&gt;直接用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;美团 Leaf&lt;/strong&gt;：&lt;a class="link" href="https://github.com/Meituan-Dianping/Leaf" target="_blank" rel="noopener"
 &gt;github.com/Meituan-Dianping/Leaf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;百度 UidGenerator&lt;/strong&gt;：&lt;a class="link" href="https://github.com/baidu/uid-generator" target="_blank" rel="noopener"
 &gt;github.com/baidu/uid-generator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不要自己手写——这些库覆盖了所有边界。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五对外暴露的业务编码"&gt;&lt;a href="#%e4%ba%94%e5%af%b9%e5%a4%96%e6%9a%b4%e9%9c%b2%e7%9a%84%e4%b8%9a%e5%8a%a1%e7%bc%96%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;五、对外暴露的&amp;quot;业务编码&amp;quot;
&lt;/h2&gt;&lt;p&gt;无论用什么主键，&lt;strong&gt;对外暴露通常都不直接用主键&lt;/strong&gt;——而是用业务编码：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;order_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 对外
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;order_no&lt;/code&gt; 用规则生成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ORD202511250001234 (前缀 + 日期 + 序号)
&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;为什么&amp;quot;对外用 order_no、对内用 id&amp;quot;？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;对外不暴露业务规模&lt;/strong&gt;——order_no 看不出实际订单数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持改格式&lt;/strong&gt;——order_no 改前缀不影响内部关联&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可读&lt;/strong&gt;——客服发&amp;quot;订单号 ORD&amp;hellip;&amp;quot;，比&amp;quot;id=12345&amp;quot;友好&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;这是国内大多数生产系统的标准姿势&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="六几个常见反模式"&gt;&lt;a href="#%e5%85%ad%e5%87%a0%e4%b8%aa%e5%b8%b8%e8%a7%81%e5%8f%8d%e6%a8%a1%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;六、几个常见反模式
&lt;/h2&gt;&lt;h3 id="反模式-1业务编码当主键--加-_old-字段做迁移"&gt;&lt;a href="#%e5%8f%8d%e6%a8%a1%e5%bc%8f-1%e4%b8%9a%e5%8a%a1%e7%bc%96%e7%a0%81%e5%bd%93%e4%b8%bb%e9%94%ae--%e5%8a%a0-_old-%e5%ad%97%e6%ae%b5%e5%81%9a%e8%bf%81%e7%a7%bb" class="header-anchor"&gt;&lt;/a&gt;反模式 1：业务编码当主键 + 加 &lt;code&gt;_old&lt;/code&gt; 字段做迁移
&lt;/h3&gt;&lt;p&gt;业务规则改了 → 加 &lt;code&gt;username_old&lt;/code&gt; 字段保留老的 → 后续查询要 &lt;code&gt;WHERE username = ? OR username_old = ?&lt;/code&gt; ——&lt;strong&gt;永远的技术债&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="反模式-2uuid-用-char36"&gt;&lt;a href="#%e5%8f%8d%e6%a8%a1%e5%bc%8f-2uuid-%e7%94%a8-char36" class="header-anchor"&gt;&lt;/a&gt;反模式 2：UUID 用 char(36)
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&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;BINARY(16)&lt;/code&gt;——空间和性能差距巨大。&lt;/p&gt;
&lt;h3 id="反模式-3自增-id-暴露给前端"&gt;&lt;a href="#%e5%8f%8d%e6%a8%a1%e5%bc%8f-3%e8%87%aa%e5%a2%9e-id-%e6%9a%b4%e9%9c%b2%e7%bb%99%e5%89%8d%e7%ab%af" class="header-anchor"&gt;&lt;/a&gt;反模式 3：自增 ID 暴露给前端
&lt;/h3&gt;&lt;p&gt;前端 URL 里 &lt;code&gt;?orderId=12345&lt;/code&gt; ——攻击者枚举可以拿到所有订单。&lt;strong&gt;用业务编码&lt;/strong&gt;或者&lt;strong&gt;对 ID 做混淆&lt;/strong&gt;（如 hashids 库）。&lt;/p&gt;
&lt;h3 id="反模式-4复合主键"&gt;&lt;a href="#%e5%8f%8d%e6%a8%a1%e5%bc%8f-4%e5%a4%8d%e5%90%88%e4%b8%bb%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;反模式 4：复合主键
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;复合主键麻烦多——外键关联痛苦、ORM 支持不好、加新维度时改表复杂。&lt;strong&gt;永远用单字段主键 + 唯一索引表达组合唯一&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="反模式-5自增--uuid-双主键"&gt;&lt;a href="#%e5%8f%8d%e6%a8%a1%e5%bc%8f-5%e8%87%aa%e5%a2%9e--uuid-%e5%8f%8c%e4%b8%bb%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;反模式 5：自增 + UUID 双主键
&lt;/h3&gt;&lt;p&gt;&amp;ldquo;我两个都要！&amp;quot;——同时维护两个 ID 维护成本高、容易产生不一致。&lt;strong&gt;单一真理来源（single source of truth）&lt;/strong&gt;——只有一个主键。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七性能数据对比"&gt;&lt;a href="#%e4%b8%83%e6%80%a7%e8%83%bd%e6%95%b0%e6%8d%ae%e5%af%b9%e6%af%94" class="header-anchor"&gt;&lt;/a&gt;七、性能数据对比
&lt;/h2&gt;&lt;p&gt;10 亿行表，单条 INSERT 的耗时（实测大致比例）：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;&lt;/th&gt;
 &lt;th style="text-align: left"&gt;写入耗时&lt;/th&gt;
 &lt;th style="text-align: left"&gt;索引大小&lt;/th&gt;
 &lt;th style="text-align: left"&gt;JOIN 速度&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;BIGINT 自增&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1×&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;雪花 ID&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1×&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;BINARY(16) UUID v7&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1.2×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;2×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1.2×&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;BINARY(16) UUID v4&lt;/td&gt;
 &lt;td style="text-align: left"&gt;5×（页分裂严重）&lt;/td&gt;
 &lt;td style="text-align: left"&gt;2×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;1.2×&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;CHAR(36) UUID&lt;/td&gt;
 &lt;td style="text-align: left"&gt;8×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;4×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;5×&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;VARCHAR 业务键&lt;/td&gt;
 &lt;td style="text-align: left"&gt;6×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;3×&lt;/td&gt;
 &lt;td style="text-align: left"&gt;4×&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;差距在大数据量场景被无限放大。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="八实战-checklist"&gt;&lt;a href="#%e5%85%ab%e5%ae%9e%e6%88%98-checklist" class="header-anchor"&gt;&lt;/a&gt;八、实战 Checklist
&lt;/h2&gt;&lt;p&gt;设计新表时按这个清单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;主键用 BIGINT&lt;/strong&gt; 自增 / 雪花 ID&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;业务编码加唯一索引&lt;/strong&gt; 但不当主键&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;VARCHAR 主键不允许&lt;/strong&gt;（除非超小表）&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;UUID 用 BINARY(16) + v7&lt;/strong&gt; 不要 v4 / char(36)&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;关联表外键用主键 ID&lt;/strong&gt;，不要用业务编码&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;对外 API 用业务编码&lt;/strong&gt;，不暴露主键&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;重要表的业务编码生成有规则&lt;/strong&gt;（前缀 + 日期 + 序号 / hashids）&lt;/li&gt;
&lt;li&gt;&lt;input disabled="" type="checkbox"&gt; &lt;strong&gt;分布式场景用雪花 ID&lt;/strong&gt;，不要自己撸&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="九关于零号-id"&gt;&lt;a href="#%e4%b9%9d%e5%85%b3%e4%ba%8e%e9%9b%b6%e5%8f%b7-id" class="header-anchor"&gt;&lt;/a&gt;九、关于&amp;quot;零号 ID&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;很多人争论 ID 应该从 0 还是 1 开始——&lt;strong&gt;约定俗成是从 1 开始&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id = 0&lt;/code&gt; 在 Java / Go 里是基本类型默认值——&lt;strong&gt;容易混淆&amp;quot;未设&amp;quot; vs &amp;ldquo;设为 0&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;业务上&amp;quot;用户 0&amp;quot;听起来怪&lt;/li&gt;
&lt;li&gt;一些 ORM 把 &lt;code&gt;id = 0&lt;/code&gt; 视为&amp;quot;新对象未保存&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;让 ID 从 1 开始是最少踩坑的约定&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&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;ol&gt;
&lt;li&gt;&lt;strong&gt;新表默认 BIGINT 自增&lt;/strong&gt;——简单可靠&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分布式上雪花 ID&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UUID 用 v7 + BINARY(16)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;绝不用 VARCHAR 业务键作主键&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;p&gt;把这些做对，你的表能跑 10 年——而不是一年后就要做&amp;quot;主键迁移&amp;quot;这种最痛的事。&lt;/p&gt;</description></item><item><title>关联用 ID 还是 CODE：数据库主键与外键设计实践</title><link>https://www.lingcoder.com/p/db-foreign-key-id-vs-code/</link><pubDate>Wed, 15 Mar 2017 15:00:00 +0800</pubDate><guid>https://www.lingcoder.com/p/db-foreign-key-id-vs-code/</guid><description>&lt;img src="https://www.lingcoder.com/p/db-foreign-key-id-vs-code/cover.svg" alt="Featured image of post 关联用 ID 还是 CODE：数据库主键与外键设计实践" /&gt;&lt;h2 id="一个常被反复争论的设计问题"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e5%b8%b8%e8%a2%ab%e5%8f%8d%e5%a4%8d%e4%ba%89%e8%ae%ba%e7%9a%84%e8%ae%be%e8%ae%a1%e9%97%ae%e9%a2%98" class="header-anchor"&gt;&lt;/a&gt;一个常被反复争论的设计问题
&lt;/h2&gt;&lt;p&gt;每个数据库表都有一个自增 &lt;code&gt;id&lt;/code&gt; 主键——这件事几乎没人会争。但&lt;strong&gt;外键到底应该引用 &lt;code&gt;id&lt;/code&gt; 还是引用业务编码（code）&lt;/strong&gt;，争论从未停止。&lt;/p&gt;
&lt;p&gt;举个最常见的例子：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 方案 A：用 ID 关联
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 关联 order_status.id
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 方案 B：用 CODE 关联
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 关联 order_status.code
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
 &lt;blockquote&gt;
 &lt;p&gt;注：示例里用 &lt;code&gt;orders&lt;/code&gt; 而不是 &lt;code&gt;order&lt;/code&gt;——&lt;code&gt;order&lt;/code&gt; 是 SQL 保留字，直接拿来当表名要么报语法错误，要么必须加反引号
&lt;code&gt;`order`&lt;/code&gt;。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;哪种好？两派都各有铁粉，争论起来谁也说服不了谁。本文把两种方案的优劣、适用边界、工程取舍讲清楚——不给标准答案，但讲清楚什么场景该选什么。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="第一原则先区分两类外键"&gt;&lt;a href="#%e7%ac%ac%e4%b8%80%e5%8e%9f%e5%88%99%e5%85%88%e5%8c%ba%e5%88%86%e4%b8%a4%e7%b1%bb%e5%a4%96%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;第一原则：先区分两类外键
&lt;/h2&gt;&lt;p&gt;讨论这个问题前必须先把&amp;quot;外键&amp;quot;分成两大类——&lt;strong&gt;它们的处理逻辑完全不同&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关联类外键&lt;/strong&gt;：链接两个&lt;strong&gt;业务实体&lt;/strong&gt;。例如 &lt;code&gt;order.user_id&lt;/code&gt; 指向 &lt;code&gt;user.id&lt;/code&gt;、&lt;code&gt;order_item.product_id&lt;/code&gt; 指向 &lt;code&gt;product.id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;枚举类外键&lt;/strong&gt;：链接到&lt;strong&gt;字典/枚举&lt;/strong&gt;。例如 &lt;code&gt;order.status&lt;/code&gt; 表示订单状态、&lt;code&gt;user.gender&lt;/code&gt; 表示性别&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart LR
 O1[order] --&gt;|user_id| User[user]
 O1 --&gt;|status_code| Dict[order_status&lt;br/&gt;字典]
 O2[order_item] --&gt;|product_id| Product[product]&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;关联类外键和枚举类外键，结论不一样。下面分开讲。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一关联类外键建议用-id"&gt;&lt;a href="#%e4%b8%80%e5%85%b3%e8%81%94%e7%b1%bb%e5%a4%96%e9%94%ae%e5%bb%ba%e8%ae%ae%e7%94%a8-id" class="header-anchor"&gt;&lt;/a&gt;一、关联类外键：建议用 ID
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;order.user_id&lt;/code&gt; 这种&amp;quot;指向另一个业务实体&amp;quot;的外键，&lt;strong&gt;强烈建议用 ID 关联&lt;/strong&gt;——也就是数据库自增主键或雪花 ID。&lt;/p&gt;
&lt;h3 id="为什么用-id"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e7%94%a8-id" class="header-anchor"&gt;&lt;/a&gt;为什么用 ID
&lt;/h3&gt;&lt;h4 id="1-id-是不变的业务编码可能变"&gt;&lt;a href="#1-id-%e6%98%af%e4%b8%8d%e5%8f%98%e7%9a%84%e4%b8%9a%e5%8a%a1%e7%bc%96%e7%a0%81%e5%8f%af%e8%83%bd%e5%8f%98" class="header-anchor"&gt;&lt;/a&gt;1. ID 是不变的，业务编码可能变
&lt;/h4&gt;&lt;p&gt;&lt;code&gt;user_no = &amp;quot;U20170315001&amp;quot;&lt;/code&gt; 这种&amp;quot;业务用户编号&amp;quot;看起来像永久标识，但&lt;strong&gt;业务发起人随时可能要求改格式&lt;/strong&gt;：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&amp;ldquo;把所有用户编号前缀从 U 改成 A，因为合并了 A 公司的系统。&amp;rdquo;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;如果 100 张表都用 &lt;code&gt;user_no&lt;/code&gt; 关联，&lt;strong&gt;改一次格式要重写 100 张表的引用&lt;/strong&gt;——可能涉及上亿行数据。如果用 ID，业务编码爱怎么变怎么变。&lt;/p&gt;
&lt;h4 id="2-数字索引比字符串索引快"&gt;&lt;a href="#2-%e6%95%b0%e5%ad%97%e7%b4%a2%e5%bc%95%e6%af%94%e5%ad%97%e7%ac%a6%e4%b8%b2%e7%b4%a2%e5%bc%95%e5%bf%ab" class="header-anchor"&gt;&lt;/a&gt;2. 数字索引比字符串索引快
&lt;/h4&gt;&lt;p&gt;数据库的 B+ Tree 索引中，整数 8 字节、字符串 32 字节起步。&lt;strong&gt;整数索引的 IO 和比较都快得多&lt;/strong&gt;——尤其在 join 大量数据时差距明显。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 整数 join
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 字符串 join
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_no&lt;/span&gt;&lt;span class="w"&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;千万级数据下，前者比后者快 30%-50% 不是稀奇事。&lt;/p&gt;
&lt;h4 id="3-id-是单调递增的写入友好"&gt;&lt;a href="#3-id-%e6%98%af%e5%8d%95%e8%b0%83%e9%80%92%e5%a2%9e%e7%9a%84%e5%86%99%e5%85%a5%e5%8f%8b%e5%a5%bd" class="header-anchor"&gt;&lt;/a&gt;3. ID 是单调递增的，写入友好
&lt;/h4&gt;&lt;p&gt;InnoDB 是按主键聚簇存储的——递增 ID 写入时&lt;strong&gt;永远是 append&lt;/strong&gt;，热点页只有一个。如果用业务编码（不一定单调），写入时容易引发**页分裂
**，影响写性能。&lt;/p&gt;
&lt;h4 id="4-不暴露业务信息"&gt;&lt;a href="#4-%e4%b8%8d%e6%9a%b4%e9%9c%b2%e4%b8%9a%e5%8a%a1%e4%bf%a1%e6%81%af" class="header-anchor"&gt;&lt;/a&gt;4. 不暴露业务信息
&lt;/h4&gt;&lt;p&gt;ID 是个&amp;quot;无意义数字&amp;quot;，不会泄漏业务规模。&lt;code&gt;order.id = 9876543&lt;/code&gt; 攻击者拿到也猜不出实际订单量；但
&lt;code&gt;order.order_no = &amp;quot;ORD-20170315-00001&amp;quot;&lt;/code&gt; 一看就知道当天第几单。&lt;/p&gt;
&lt;h3 id="但-id-也不是完美"&gt;&lt;a href="#%e4%bd%86-id-%e4%b9%9f%e4%b8%8d%e6%98%af%e5%ae%8c%e7%be%8e" class="header-anchor"&gt;&lt;/a&gt;但 ID 也不是完美
&lt;/h3&gt;&lt;h4 id="1-跨库迁移痛"&gt;&lt;a href="#1-%e8%b7%a8%e5%ba%93%e8%bf%81%e7%a7%bb%e7%97%9b" class="header-anchor"&gt;&lt;/a&gt;1. 跨库迁移痛
&lt;/h4&gt;&lt;p&gt;ID 是数据库自增，&lt;strong&gt;两个库的数据合并时 ID 必然冲突&lt;/strong&gt;——常见做法是用雪花 ID 或一开始就用 UUID。&lt;/p&gt;
&lt;h4 id="2-读起来不友好"&gt;&lt;a href="#2-%e8%af%bb%e8%b5%b7%e6%9d%a5%e4%b8%8d%e5%8f%8b%e5%a5%bd" class="header-anchor"&gt;&lt;/a&gt;2. 读起来不友好
&lt;/h4&gt;&lt;p&gt;日志里看到 &lt;code&gt;user_id=12345&lt;/code&gt; 你要去查另一张表才知道是谁。所以&lt;strong&gt;业务对外的 API 通常还是返回 user_no&lt;/strong&gt;——内部 ID + 外部 code
双轨制。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二枚举类外键建议用-code"&gt;&lt;a href="#%e4%ba%8c%e6%9e%9a%e4%b8%be%e7%b1%bb%e5%a4%96%e9%94%ae%e5%bb%ba%e8%ae%ae%e7%94%a8-code" class="header-anchor"&gt;&lt;/a&gt;二、枚举类外键：建议用 CODE
&lt;/h2&gt;&lt;p&gt;订单状态、性别、城市编码这种&amp;quot;指向字典&amp;quot;的外键，&lt;strong&gt;反过来——建议用 CODE 关联&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="为什么用-code"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e7%94%a8-code" class="header-anchor"&gt;&lt;/a&gt;为什么用 CODE
&lt;/h3&gt;&lt;h4 id="1-可读性极强"&gt;&lt;a href="#1-%e5%8f%af%e8%af%bb%e6%80%a7%e6%9e%81%e5%bc%ba" class="header-anchor"&gt;&lt;/a&gt;1. 可读性极强
&lt;/h4&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 用 ID
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;-- 用 CODE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;PAID&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;不用 join 字典表都能看懂&amp;quot;已支付&amp;quot;。&lt;strong&gt;线上排查问题时这个差别非常大&lt;/strong&gt;。&lt;/p&gt;
&lt;h4 id="2-字典基本不会改"&gt;&lt;a href="#2-%e5%ad%97%e5%85%b8%e5%9f%ba%e6%9c%ac%e4%b8%8d%e4%bc%9a%e6%94%b9" class="header-anchor"&gt;&lt;/a&gt;2. 字典基本不会改
&lt;/h4&gt;&lt;p&gt;&lt;code&gt;order_status&lt;/code&gt; 这种字典——&lt;code&gt;PAID&lt;/code&gt;、&lt;code&gt;SHIPPED&lt;/code&gt;、&lt;code&gt;COMPLETED&lt;/code&gt;——一旦确定，几乎不会变（业务有变化也是新增，不是修改）。&lt;strong&gt;code
不会改的前提下，ID 的&amp;quot;不变性优势&amp;quot;就消失了&lt;/strong&gt;。&lt;/p&gt;
&lt;h4 id="3-跨系统一致"&gt;&lt;a href="#3-%e8%b7%a8%e7%b3%bb%e7%bb%9f%e4%b8%80%e8%87%b4" class="header-anchor"&gt;&lt;/a&gt;3. 跨系统一致
&lt;/h4&gt;&lt;p&gt;字典经常要给前端、第三方对接、数据导出。&lt;strong&gt;&lt;code&gt;PAID&lt;/code&gt; 比 &lt;code&gt;3&lt;/code&gt; 通用得多&lt;/strong&gt;——前端不用维护&amp;quot;3=已支付&amp;quot;的对照表，直接看 code 写逻辑。&lt;/p&gt;
&lt;h4 id="4-代码层面更清爽"&gt;&lt;a href="#4-%e4%bb%a3%e7%a0%81%e5%b1%82%e9%9d%a2%e6%9b%b4%e6%b8%85%e7%88%bd" class="header-anchor"&gt;&lt;/a&gt;4. 代码层面更清爽
&lt;/h4&gt;&lt;p&gt;业务代码用枚举 + 字符串 code 是天然契合：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;UNPAID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UNPAID&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PAID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;PAID&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SHIPPED&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SHIPPED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;COMPLETED&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;COMPLETED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// SQL 里直接 status_code = &amp;#39;PAID&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Java 里 status == OrderStatus.PAID&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 前端直接 status === &amp;#39;PAID&amp;#39;&lt;/span&gt;&lt;span class="w"&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;如果用 ID，代码里到处是 &lt;code&gt;status_id == 3&lt;/code&gt; 这种&lt;strong&gt;魔法数字&lt;/strong&gt;，可读性差，加新状态时容易和已有 ID 撞。&lt;/p&gt;
&lt;h4 id="5-字典表本身也得有"&gt;&lt;a href="#5-%e5%ad%97%e5%85%b8%e8%a1%a8%e6%9c%ac%e8%ba%ab%e4%b9%9f%e5%be%97%e6%9c%89" class="header-anchor"&gt;&lt;/a&gt;5. 字典表本身也得有
&lt;/h4&gt;&lt;p&gt;国内项目还有种做法是&lt;strong&gt;根本不建字典表，code 直接写在代码枚举里&lt;/strong&gt;。这有它的优势——一致性强、没有&amp;quot;代码和数据库不同步&amp;quot;的问题，但缺点是
&lt;strong&gt;修改要发版&lt;/strong&gt;，前端没法动态拉取选项。&lt;/p&gt;
&lt;p&gt;工程上常见做法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简单字典直接代码枚举&lt;/li&gt;
&lt;li&gt;需要后台管理、能配置的字典建表（status_code 作为 PK 或唯一索引）&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="三混用一种实用模式"&gt;&lt;a href="#%e4%b8%89%e6%b7%b7%e7%94%a8%e4%b8%80%e7%a7%8d%e5%ae%9e%e7%94%a8%e6%a8%a1%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;三、混用：一种实用模式
&lt;/h2&gt;&lt;p&gt;很多项目其实是混用——&lt;strong&gt;业务实体用 ID 关联，枚举用 code&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;order_no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 业务编号，对外用
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 关联 user.id（关联类）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;-- 关联 order_status.code（枚举类）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;这是一种&amp;quot;按字段类型选方案&amp;quot;的混合姿势，比&amp;quot;全 ID&amp;quot;或&amp;quot;全 CODE&amp;quot;都更贴合实际。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四第三种争论uuid--雪花-id"&gt;&lt;a href="#%e5%9b%9b%e7%ac%ac%e4%b8%89%e7%a7%8d%e4%ba%89%e8%ae%bauuid--%e9%9b%aa%e8%8a%b1-id" class="header-anchor"&gt;&lt;/a&gt;四、第三种争论：UUID / 雪花 ID
&lt;/h2&gt;&lt;p&gt;Long 自增 ID 适合单库，分布式场景常被替换为 UUID 或雪花 ID。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;方案&lt;/th&gt;
 &lt;th style="text-align: left"&gt;优点&lt;/th&gt;
 &lt;th style="text-align: left"&gt;缺点&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;自增 ID&lt;/td&gt;
 &lt;td style="text-align: left"&gt;紧凑、有序、写入友好&lt;/td&gt;
 &lt;td style="text-align: left"&gt;单库限制、跨库迁移痛&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;UUID&lt;/td&gt;
 &lt;td style="text-align: left"&gt;全局唯一、无中心化&lt;/td&gt;
 &lt;td style="text-align: left"&gt;二进制 16 字节 / 文本 36 字符、v4 无序、写入页分裂、读取慢&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;雪花 ID&lt;/td&gt;
 &lt;td style="text-align: left"&gt;紧凑（Long）、有序、分布式&lt;/td&gt;
 &lt;td style="text-align: left"&gt;时钟回拨问题、需要部署机器号&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;关联类外键用什么主键，CODE 关联问题不大&lt;/strong&gt;。但用 UUID 时务必走 &lt;code&gt;BINARY(16)&lt;/code&gt; 存储而不是 &lt;code&gt;CHAR(36)&lt;/code&gt;
——前者只占一半空间且索引性能高得多（不止一倍）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五外键约束要不要加-fk-constraint"&gt;&lt;a href="#%e4%ba%94%e5%a4%96%e9%94%ae%e7%ba%a6%e6%9d%9f%e8%a6%81%e4%b8%8d%e8%a6%81%e5%8a%a0-fk-constraint" class="header-anchor"&gt;&lt;/a&gt;五、外键约束：要不要加 FK constraint
&lt;/h2&gt;&lt;p&gt;讨论&amp;quot;用 ID 还是 CODE&amp;quot;时，常常顺带聊另一个问题——&lt;strong&gt;要不要在数据库层面建外键约束&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fk_user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FOREIGN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REFERENCES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/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;ul&gt;
&lt;li&gt;&lt;strong&gt;性能影响&lt;/strong&gt;：每次写入都要校验外键，高并发下是负担&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分库分表后约束失效&lt;/strong&gt;：跨库 FK 是不可能的&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;表设计上仍然要有&amp;quot;外键&amp;quot;概念&lt;/li&gt;
&lt;li&gt;命名规范要清晰：&lt;code&gt;user_id&lt;/code&gt; / &lt;code&gt;product_id&lt;/code&gt; 这种命名让人一眼看出关联&lt;/li&gt;
&lt;li&gt;数据一致性靠应用代码 + 单测覆盖&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="六实战建议"&gt;&lt;a href="#%e5%85%ad%e5%ae%9e%e6%88%98%e5%bb%ba%e8%ae%ae" class="header-anchor"&gt;&lt;/a&gt;六、实战建议
&lt;/h2&gt;&lt;pre class="mermaid" style="visibility:hidden"&gt;flowchart TD
 Start([这个外键关联什么?])
 Start --&gt; Type{业务实体 or 枚举字典?}
 Type --&gt;|业务实体| ID[用 ID 关联&lt;br/&gt;同时保留 业务编码 字段对外]
 Type --&gt;|枚举字典| CHANGE{字典变化频率?}
 CHANGE --&gt;|几乎不变| CODE[用 CODE，不依赖字典表]
 CHANGE --&gt;|偶尔会改| BOTH[code 关联，但字典建表持久化]&lt;/pre&gt;&lt;p&gt;具体几条建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;业务实体外键用 ID&lt;/strong&gt;——&lt;code&gt;user_id&lt;/code&gt;、&lt;code&gt;product_id&lt;/code&gt;、&lt;code&gt;order_id&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;枚举/字典外键用 CODE&lt;/strong&gt;——&lt;code&gt;status_code&lt;/code&gt;、&lt;code&gt;type_code&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;xxx_no&lt;/code&gt; / &lt;code&gt;xxx_code&lt;/code&gt; 字段建唯一索引&lt;/strong&gt;——业务上对外暴露但 DB 关联用 ID&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产环境基本都不加 FK 约束&lt;/strong&gt;——逻辑外键 + 应用层校验&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要把&amp;quot;自增 ID&amp;quot;暴露给前端&lt;/strong&gt;——容易暴露业务规模 + 不好做加密短链&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨库场景上雪花 ID 不要 UUID&lt;/strong&gt;——写入性能差距很大&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="七一些反例"&gt;&lt;a href="#%e4%b8%83%e4%b8%80%e4%ba%9b%e5%8f%8d%e4%be%8b" class="header-anchor"&gt;&lt;/a&gt;七、一些反例
&lt;/h2&gt;&lt;h3 id="反例-1user_no-当外键"&gt;&lt;a href="#%e5%8f%8d%e4%be%8b-1user_no-%e5%bd%93%e5%a4%96%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;反例 1：&lt;code&gt;user_no&lt;/code&gt; 当外键
&lt;/h3&gt;&lt;p&gt;某项目用 &lt;code&gt;U20170315001&lt;/code&gt; 这种用户编号当所有外键。三年后业务并购，要把所有 U 开头的用户改成 A 开头——&lt;strong&gt;全表 update + 全部关联表
update&lt;/strong&gt;，停服 12 小时。&lt;/p&gt;
&lt;p&gt;教训：&lt;strong&gt;业务编号永远可能改格式&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="反例-2状态用-id"&gt;&lt;a href="#%e5%8f%8d%e4%be%8b-2%e7%8a%b6%e6%80%81%e7%94%a8-id" class="header-anchor"&gt;&lt;/a&gt;反例 2：状态用 ID
&lt;/h3&gt;&lt;p&gt;订单状态字典表里 &lt;code&gt;1=未付款&lt;/code&gt;、&lt;code&gt;2=已付款&lt;/code&gt;、&lt;code&gt;3=已发货&lt;/code&gt;。某天产品提需求：&amp;ldquo;把&amp;rsquo;已付款&amp;rsquo;拆成&amp;rsquo;部分付款&amp;rsquo;和&amp;rsquo;全额付款&amp;rsquo;&amp;quot;。开发以为只要在字典加一条，
&lt;strong&gt;结果全代码搜 &lt;code&gt;status_id == 2&lt;/code&gt; 漏改了三处&lt;/strong&gt;——线上有&amp;quot;全额付款的订单被识别为部分付款&amp;rdquo;。&lt;/p&gt;
&lt;p&gt;教训：&lt;strong&gt;枚举用数字 ID 容易让代码里有魔法数字&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="反例-3用业务编码当主键"&gt;&lt;a href="#%e5%8f%8d%e4%be%8b-3%e7%94%a8%e4%b8%9a%e5%8a%a1%e7%bc%96%e7%a0%81%e5%bd%93%e4%b8%bb%e9%94%ae" class="header-anchor"&gt;&lt;/a&gt;反例 3：用业务编码当主键
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;order_no&lt;/code&gt; 设为 &lt;code&gt;order&lt;/code&gt; 表的主键——单字段、有序、看似优雅。但：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VARCHAR 主键聚簇索引大、IO 多&lt;/li&gt;
&lt;li&gt;业务编码格式偶尔要改（前缀、长度）→ 主键变化引发整表 rebuild&lt;/li&gt;
&lt;li&gt;子表关联用 VARCHAR(32) 占空间多&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;教训：&lt;strong&gt;主键应当稳定、无意义、紧凑&lt;/strong&gt;——&lt;code&gt;bigint id&lt;/code&gt; 是最佳形态。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="小结"&gt;&lt;a href="#%e5%b0%8f%e7%bb%93" class="header-anchor"&gt;&lt;/a&gt;小结
&lt;/h2&gt;&lt;p&gt;把全文压一句：&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;业务实体之间用 ID 关联，枚举字典之间用 CODE 关联——稳定的东西用稳定的标识，可读的东西用可读的标识。&lt;/strong&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;记住几条工程铁律：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ID 主键永不暴露给业务规则&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;业务编码做唯一索引但不做外键&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;枚举字典 code 全大写下划线，永不复用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产环境逻辑外键 + 应用层校验&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;代码里别留魔法数字，永远用枚举 + code 比较&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;把这几条吃透，数据库设计这层基本不会出大问题。&lt;/p&gt;</description></item></channel></rss>