偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

用雪花 id 和 uuid 做 MySQL 主鍵,被領(lǐng)導(dǎo)懟了

數(shù)據(jù)庫(kù) MySQL
技術(shù)沒(méi)有好壞之分,只有合適不合適。別看到別人用雪花 ID、用 UUID,你就跟著用,得先搞明白背后的原理,結(jié)合自己的業(yè)務(wù)場(chǎng)景,才能做出正確的選擇。不然哪天被領(lǐng)導(dǎo)懟了,還不知道為啥,多冤?。?

兄弟們,上周三下午,我正對(duì)著電腦美滋滋地敲代碼,突然背后傳來(lái)一聲 “你這主鍵用的啥?”—— 回頭一看,領(lǐng)導(dǎo)正皺著眉盯著我屏幕上的 MySQL 表結(jié)構(gòu)。我挺得意地說(shuō) “用的雪花 ID 啊,分布式環(huán)境下唯一,多高級(jí)”,結(jié)果領(lǐng)導(dǎo)當(dāng)場(chǎng)就懟了我一句:“高級(jí)?你知道這玩意兒給 MySQL 挖坑有多深嗎?”

當(dāng)時(shí)我臉一下子就紅了,心里還嘀咕 “不就是個(gè)主鍵嗎,能有啥大問(wèn)題”,但后來(lái)跟著領(lǐng)導(dǎo)扒了半天原理,又自己做了測(cè)試,才發(fā)現(xiàn)原來(lái)選主鍵這事兒,真不是 “能生成唯一 ID 就行” 這么簡(jiǎn)單。今天就把我踩過(guò)的坑、搞懂的門(mén)道都跟大家掰扯掰扯,省得你們跟我一樣,被領(lǐng)導(dǎo)懟了還不知道為啥。

先搞明白:MySQL 主鍵到底要的是啥?

在說(shuō)雪花 ID 和 UUID 之前,咱得先統(tǒng)一個(gè)認(rèn)知 ——MySQL(尤其是咱們常用的 InnoDB 引擎)對(duì)主鍵的要求,跟找對(duì)象似的,得 “門(mén)當(dāng)戶對(duì)” 才行。你不能光看 “唯一” 這一個(gè)優(yōu)點(diǎn),就不管其他條件了,不然早晚得出問(wèn)題。

InnoDB 這引擎有個(gè)很關(guān)鍵的特性叫 “聚簇索引”,簡(jiǎn)單說(shuō)就是 “主鍵索引和數(shù)據(jù)行綁在一塊兒”。你可以把它理解成一本書(shū),主鍵索引就是目錄,數(shù)據(jù)行就是正文內(nèi)容,目錄的順序和正文的順序是完全對(duì)應(yīng)的。要是目錄亂序,你找內(nèi)容的時(shí)候就得翻來(lái)翻去;要是目錄順序整齊,一下就能定位到地方。

所以 MySQL 主鍵的核心要求就三個(gè),少一個(gè)都不行:

1. 唯一性:這是底線,沒(méi)商量

不管是自增 ID、雪花 ID 還是 UUID,首先得保證 “不重復(fù)”。你總不能讓兩個(gè)數(shù)據(jù)共用一個(gè)主鍵吧?就像每個(gè)人的身份證號(hào)不能一樣,不然銀行取錢(qián)都能取錯(cuò),這是最基本的要求,沒(méi)啥好說(shuō)的。

2. 有序性:這是性能的關(guān)鍵,很多人都忽略

剛才說(shuō)的聚簇索引,要是主鍵是有序的,比如自增 ID 1、2、3、4……InnoDB 插入數(shù)據(jù)的時(shí)候,就知道直接往最后面加就行,跟排隊(duì)似的,順著來(lái),效率特別高。

但要是主鍵是無(wú)序的,比如 UUID 那種長(zhǎng)得亂七八糟的字符串,插入的時(shí)候就麻煩了 ——InnoDB 得先找這個(gè) ID 該插在哪個(gè)位置,可能插在中間某個(gè)地方,這時(shí)候就需要 “頁(yè)分裂”。啥是頁(yè)分裂?你可以想象成書(shū)架上的書(shū)排滿了,你要把一本新書(shū)插進(jìn)中間,就得先把后面的書(shū)都往后挪一挪,騰出地方。數(shù)據(jù)量小的時(shí)候還好,數(shù)據(jù)量大了,這挪來(lái)挪去的功夫可就多了,插入速度會(huì)越來(lái)越慢,索引還會(huì)變得特別臃腫。

3. 占用空間?。涸叫≡胶?,別給數(shù)據(jù)庫(kù)添負(fù)擔(dān)

主鍵是要存在索引里的,而且二級(jí)索引(比如你建的 name 索引、age 索引)里存的也是主鍵的值。要是主鍵占用空間大,比如 UUID 是 36 個(gè)字符,那索引文件就會(huì)變得特別大,不僅占磁盤(pán)空間,還會(huì)影響查詢速度 —— 因?yàn)閮?nèi)存里能裝下的索引數(shù)據(jù)少了,得頻繁去磁盤(pán)讀數(shù)據(jù),速度能不慢嗎?

搞懂這三個(gè)要求,咱們?cè)倩仡^看雪花 ID 和 UUID,為啥用它們做 MySQL 主鍵會(huì)被領(lǐng)導(dǎo)懟,就一目了然了。

先扒 UUID:看著萬(wàn)能,實(shí)則是 MySQL 的 “空間刺客”

咱們先說(shuō)說(shuō) UUID,這玩意兒全稱(chēng)是 “通用唯一識(shí)別碼”,格式大概是這樣的:550e8400-e29b-41d4-a716-446655440000,一共 36 個(gè)字符,看著挺唬人,而且確實(shí)能保證全球唯一,不管你多少臺(tái)機(jī)器生成,都不會(huì)重復(fù)。

很多剛接觸分布式的同學(xué),一聽(tīng)說(shuō)要保證 ID 唯一,第一個(gè)想到的就是 UUID,覺(jué)得 “這玩意兒不用配置,拿來(lái)就用,多方便”。但你要是把它當(dāng) MySQL 主鍵,麻煩就來(lái)了。

問(wèn)題 1:無(wú)序性直接觸發(fā) “頁(yè)分裂地獄”

UUID 最大的問(wèn)題就是 “無(wú)序”—— 你生成的兩個(gè) UUID,誰(shuí)大誰(shuí)小完全沒(méi)規(guī)律。比如你剛插入一個(gè)550e8400開(kāi)頭的,下一個(gè)可能是a7164466開(kāi)頭的,再下一個(gè)又可能是12345678開(kāi)頭的。

這對(duì) InnoDB 的聚簇索引來(lái)說(shuō),簡(jiǎn)直是災(zāi)難。我之前做過(guò)一個(gè)測(cè)試:用 UUID 當(dāng)主鍵,往 MySQL 里插入 100 萬(wàn)條數(shù)據(jù),前 10 萬(wàn)條的時(shí)候還挺順暢,插入速度大概每秒 1 萬(wàn)條;但到了 50 萬(wàn)條之后,速度就掉到每秒 3000 條了;到 100 萬(wàn)條的時(shí)候,每秒只能插 1000 多條,而且磁盤(pán) IO 占用率直接飆到 90% 以上。

后來(lái)我用工具查了一下索引情況,發(fā)現(xiàn)索引的 “碎片率” 高達(dá) 60%—— 這就是頁(yè)分裂搞的鬼。因?yàn)槊看尾迦攵家矓?shù)據(jù),索引里全是碎片,就像你衣柜里的衣服亂堆一樣,找的時(shí)候特別費(fèi)勁。

反觀用自增 ID 做主鍵,插入 100 萬(wàn)條數(shù)據(jù),速度一直穩(wěn)定在每秒 1.5 萬(wàn)條左右,索引碎片率只有 5% 不到。這差距,可不是一星半點(diǎn)。

問(wèn)題 2:36 個(gè)字符的 “空間黑洞”,太費(fèi)資源

UUID 是 36 個(gè)字符,要是用 VARCHAR (36) 存儲(chǔ),每個(gè) UUID 要占用 36 個(gè)字節(jié)(要是用 UTF-8 編碼,還可能更多)。咱們來(lái)算筆賬:

假設(shè)你有一張用戶表,有 1000 萬(wàn)條數(shù)據(jù),主鍵是 UUID,那光主鍵索引就要占用 1000 萬(wàn) × 36 字節(jié) = 360MB。要是你再建幾個(gè)二級(jí)索引,比如 name、phone、email,每個(gè)二級(jí)索引里都要存主鍵的值,那每個(gè)二級(jí)索引又要多占 360MB,幾個(gè)索引加起來(lái),光索引文件就好幾 GB 了。

要是換成自增 ID,用 BIGINT 類(lèi)型(8 個(gè)字節(jié)),同樣 1000 萬(wàn)條數(shù)據(jù),主鍵索引只需要 1000 萬(wàn) × 8 字節(jié) = 80MB,二級(jí)索引也跟著變小。同樣的磁盤(pán)空間,能裝下更多的數(shù)據(jù)和索引,查詢的時(shí)候內(nèi)存也能緩存更多索引,速度自然就快了。

有些同學(xué)可能會(huì)說(shuō) “我可以把 UUID 轉(zhuǎn)成二進(jìn)制存儲(chǔ)啊,這樣占用空間就小了”。沒(méi)錯(cuò),UUID 轉(zhuǎn)成二進(jìn)制是能從 36 字節(jié)降到 16 字節(jié),但還是比自增 ID 的 8 字節(jié)大一倍,而且還有個(gè)更麻煩的問(wèn)題:查詢的時(shí)候你得把 UUID 轉(zhuǎn)成二進(jìn)制才能查,寫(xiě) SQL 的時(shí)候特別麻煩,比如where id = UNHEX('550e8400-e29b-41d4-a716-446655440000'),不僅容易寫(xiě)錯(cuò),而且可讀性極差,后續(xù)維護(hù)的時(shí)候,同事看到這種 SQL 得罵娘。

問(wèn)題 3:查詢性能差,尤其是范圍查詢

咱們平時(shí)查數(shù)據(jù),經(jīng)常會(huì)用范圍查詢,比如 “查昨天注冊(cè)的用戶”,要是主鍵是自增 ID,因?yàn)?ID 是有序的,InnoDB 直接就能定位到昨天的 ID 范圍,快速查出數(shù)據(jù)。

但要是主鍵是 UUID,ID 是無(wú)序的,就算你按主鍵范圍查,InnoDB 也得全表掃描(或者掃描大部分索引),因?yàn)樗恢肋@些 UUID 的范圍對(duì)應(yīng)的數(shù)據(jù)在哪里。我之前做過(guò)測(cè)試,查 “最近 1 萬(wàn)條數(shù)據(jù)”,自增 ID 主鍵只需要 0.02 秒,而 UUID 主鍵需要 0.8 秒,慢了 40 倍!

那 UUID 就一點(diǎn)用都沒(méi)有了嗎?也不是。比如你在分布式系統(tǒng)里給文件命名、給緩存鍵命名,這些場(chǎng)景不需要存在 MySQL 里,也不需要排序,用 UUID 就很合適。但要是當(dāng) MySQL 主鍵,那還是算了吧,純屬給自己找罪受。

再聊雪花 ID:比 UUID 靠譜,但坑也不少

說(shuō)完 UUID,咱們?cè)僬f(shuō)說(shuō)雪花 ID。雪花 ID 是 Twitter 搞出來(lái)的一種分布式 ID 生成算法,結(jié)構(gòu)是 64 位的長(zhǎng)整型(BIGINT),格式大概是這樣的:

  • 1 位符號(hào)位:固定 0,因?yàn)?ID 是正數(shù)
  • 41 位時(shí)間戳:能表示大概 69 年的時(shí)間(從某個(gè)起始時(shí)間開(kāi)始算)
  • 10 位機(jī)器 ID:能表示 1024 臺(tái)機(jī)器
  • 12 位序列號(hào):每臺(tái)機(jī)器每秒能生成 4096 個(gè) ID(12 位最多 4095)

雪花 ID 的優(yōu)點(diǎn)很明顯:是有序的(因?yàn)橛袝r(shí)間戳)、占用空間?。? 字節(jié),和自增 ID 一樣)、能保證分布式環(huán)境下唯一,看起來(lái)好像完美符合 MySQL 主鍵的要求,那為啥我用雪花 ID 還會(huì)被領(lǐng)導(dǎo)懟呢?

別著急,雪花 ID 的坑,比你想象的要多。

問(wèn)題 1:時(shí)鐘回?fù)苁?“致命傷”

雪花 ID 的有序性,全靠前面的 41 位時(shí)間戳。但要是生成 ID 的機(jī)器出現(xiàn) “時(shí)鐘回?fù)堋?,麻煩就大了?/p>

啥是時(shí)鐘回?fù)埽烤褪菣C(jī)器的系統(tǒng)時(shí)間突然往后跳了,比如本來(lái)是 2025 年 8 月 25 日,突然變成 2025 年 8 月 24 日了。這可能是因?yàn)闄C(jī)器同步了 NTP 服務(wù)器時(shí)間,也可能是系統(tǒng)出了故障。

一旦發(fā)生時(shí)鐘回?fù)?,雪?ID 生成的時(shí)間戳就會(huì)比之前的小,生成的 ID 就會(huì)比之前的小,變成 “無(wú)序” 的。要是把這種無(wú)序的 ID 插進(jìn) MySQL,就會(huì)出現(xiàn)和 UUID 類(lèi)似的問(wèn)題:頁(yè)分裂、插入速度變慢。

更嚴(yán)重的是,要是時(shí)鐘回?fù)艿臅r(shí)間比較長(zhǎng),還可能生成重復(fù)的 ID。比如機(jī)器 A 在 8 月 25 日 10 點(diǎn)生成了一個(gè) ID,然后時(shí)鐘回?fù)艿?8 月 25 日 9 點(diǎn),又生成了一個(gè) ID,這兩個(gè) ID 的時(shí)間戳、機(jī)器 ID、序列號(hào)都可能一樣,導(dǎo)致主鍵重復(fù),插入數(shù)據(jù)直接報(bào)錯(cuò)。

我之前就遇到過(guò)這種情況:有個(gè)項(xiàng)目用了雪花 ID 當(dāng)主鍵,有一次服務(wù)器重啟后,NTP 同步時(shí)間,時(shí)鐘回?fù)芰?10 分鐘,結(jié)果當(dāng)天下午插入數(shù)據(jù)的時(shí)候,報(bào)了好幾百次主鍵沖突錯(cuò)誤,查了半天才發(fā)現(xiàn)是時(shí)鐘回?fù)芨愕墓怼?/p>

那怎么解決時(shí)鐘回?fù)軉?wèn)題呢?有幾種方案,但都不完美:

  • 方案 1:檢測(cè)到時(shí)鐘回?fù)芫蜁和I?ID,等時(shí)間追上了再繼續(xù)。但這樣會(huì)導(dǎo)致服務(wù)暫時(shí)不可用,要是在高并發(fā)場(chǎng)景下,比如秒殺活動(dòng),這絕對(duì)是災(zāi)難。
  • 方案 2:用物理時(shí)鐘 + 邏輯時(shí)鐘結(jié)合的方式,比如記錄上次生成 ID 的時(shí)間戳,要是當(dāng)前時(shí)間戳比上次小,就用上次的時(shí)間戳 + 1。但這樣會(huì)導(dǎo)致 ID 的時(shí)間戳和實(shí)際時(shí)間不一致,后續(xù)要是想通過(guò) ID 判斷數(shù)據(jù)生成時(shí)間,就不準(zhǔn)了。
  • 方案 3:多機(jī)房部署的時(shí)候,給每個(gè)機(jī)房分配不同的機(jī)器 ID 段,就算某個(gè)機(jī)房時(shí)鐘回?fù)埽膊粫?huì)和其他機(jī)房的 ID 重復(fù)。但這需要復(fù)雜的配置和管理,小團(tuán)隊(duì)玩不轉(zhuǎn)。

不管哪種方案,都需要額外的開(kāi)發(fā)和維護(hù)成本,不像自增 ID 那樣 “拿來(lái)就用,啥都不用管”。

問(wèn)題 2:機(jī)器 ID 配置不當(dāng),分分鐘重復(fù)

雪花 ID 的 10 位機(jī)器 ID,能表示 1024 臺(tái)機(jī)器。但要是你配置機(jī)器 ID 的時(shí)候不小心,把兩臺(tái)機(jī)器配置成了同一個(gè) ID,那這兩臺(tái)機(jī)器生成的雪花 ID 就會(huì)重復(fù),插入 MySQL 的時(shí)候就會(huì)報(bào)主鍵沖突。

我之前見(jiàn)過(guò)一個(gè)團(tuán)隊(duì),為了圖省事,直接用機(jī)器的 IP 地址最后幾位當(dāng)機(jī)器 ID。結(jié)果有一次擴(kuò)容,新增的機(jī)器 IP 最后幾位和之前的機(jī)器重復(fù)了,導(dǎo)致生成的雪花 ID 重復(fù),線上數(shù)據(jù)插入失敗,排查了 3 個(gè)小時(shí)才找到原因,最后還得回滾數(shù)據(jù),別提多狼狽了。

那機(jī)器 ID 該怎么配置呢?正確的做法是:

  • 用 ZooKeeper、Etcd 這類(lèi)分布式協(xié)調(diào)工具,給每臺(tái)機(jī)器分配唯一的機(jī)器 ID,機(jī)器啟動(dòng)的時(shí)候去申請(qǐng),關(guān)閉的時(shí)候釋放。
  • 要是沒(méi)有分布式協(xié)調(diào)工具,也可以手動(dòng)分配機(jī)器 ID 段,比如給 A 機(jī)房分配 0-100,B 機(jī)房分配 101-200,每臺(tái)機(jī)器在自己的段里選一個(gè)唯一的 ID。

但不管哪種方式,都需要額外的配置和維護(hù),不像自增 ID 那樣 “零配置”。

問(wèn)題 3:在某些場(chǎng)景下,有序性也會(huì)出問(wèn)題

雪花 ID 的有序性,是 “相對(duì)有序”,不是 “絕對(duì)有序”。因?yàn)樗呐判騼?yōu)先級(jí)是:時(shí)間戳 > 機(jī)器 ID > 序列號(hào)。

也就是說(shuō),在同一毫秒內(nèi),不同機(jī)器生成的 ID,會(huì)按機(jī)器 ID 排序;同一機(jī)器同一毫秒內(nèi)生成的 ID,會(huì)按序列號(hào)排序。

這在大部分場(chǎng)景下沒(méi)問(wèn)題,但要是你有 “嚴(yán)格按生成時(shí)間排序” 的需求,就可能出問(wèn)題。比如你有一個(gè)訂單表,要求訂單 ID 嚴(yán)格按下單時(shí)間排序,要是兩臺(tái)機(jī)器在同一毫秒內(nèi)生成訂單 ID,機(jī)器 ID 大的那個(gè),就算下單時(shí)間稍晚,ID 也會(huì)更大,導(dǎo)致訂單 ID 的順序和實(shí)際下單時(shí)間的順序不一致。

雖然這種情況出現(xiàn)的概率不高,但要是你的業(yè)務(wù)對(duì) ID 的時(shí)間順序要求特別嚴(yán)格(比如金融場(chǎng)景),那雪花 ID 就不太合適了。

問(wèn)題 4:遷移數(shù)據(jù)的時(shí)候,能讓你哭

要是你用雪花 ID 當(dāng)主鍵,后續(xù)遷移數(shù)據(jù)的時(shí)候,比如把數(shù)據(jù)從舊庫(kù)遷到新庫(kù),或者分庫(kù)分表,就會(huì)遇到一個(gè)麻煩:雪花 ID 是在應(yīng)用層生成的,不是數(shù)據(jù)庫(kù)生成的,所以遷移的時(shí)候,你得保證新庫(kù)的 ID 和舊庫(kù)一致,不能重復(fù),也不能漏。

而要是用自增 ID,數(shù)據(jù)庫(kù)會(huì)自動(dòng)生成唯一的 ID,遷移的時(shí)候只需要把數(shù)據(jù)導(dǎo)過(guò)去就行,不用管 ID 的問(wèn)題。

我之前參與過(guò)一個(gè)項(xiàng)目的數(shù)據(jù)庫(kù)遷移,用的就是雪花 ID 當(dāng)主鍵,結(jié)果遷移過(guò)程中,因?yàn)橛胁糠謹(jǐn)?shù)據(jù)的 ID 重復(fù),導(dǎo)致遷移失敗,最后不得不寫(xiě)了個(gè)腳本,先把舊庫(kù)的 ID 全部導(dǎo)出來(lái),再和新庫(kù)的 ID 對(duì)比,花了整整兩天才搞定,要是用自增 ID,半天就能搞定。

那 MySQL 主鍵到底該用啥?這 3 個(gè)方案才是王道

既然 UUID 和雪花 ID 當(dāng) MySQL 主鍵都有這么多坑,那到底該用啥呢?別著急,領(lǐng)導(dǎo)后來(lái)給我推薦了 3 個(gè)方案,親測(cè)好用,咱們一個(gè)個(gè)說(shuō)。

方案 1:小項(xiàng)目 / 單機(jī)項(xiàng)目,自增 IDyyds

要是你的項(xiàng)目是小項(xiàng)目,或者不需要分布式部署,就一臺(tái) MySQL 服務(wù)器,那自增 ID(AUTO_INCREMENT)絕對(duì)是最佳選擇,沒(méi)有之一。

自增 ID 的優(yōu)點(diǎn)太多了:

  • 完全符合 MySQL 主鍵的三個(gè)要求:唯一(數(shù)據(jù)庫(kù)保證)、有序(每次 + 1)、占用空間?。? 字節(jié))。
  • 零配置:不用自己寫(xiě)代碼生成 ID,數(shù)據(jù)庫(kù)自動(dòng)搞定,省事兒。
  • 性能好:插入速度快,查詢速度快,索引碎片少。
  • 方便遷移:遷移數(shù)據(jù)的時(shí)候不用管 ID,數(shù)據(jù)庫(kù)自動(dòng)生成。

那自增 ID 就沒(méi)缺點(diǎn)嗎?也有,比如:

  • 分布式環(huán)境下不唯一:要是你有多個(gè) MySQL 實(shí)例,每個(gè)實(shí)例都自增,就會(huì)出現(xiàn)重復(fù)的 ID。
  • 容易被猜到:比如你的用戶 ID 是自增的,別人很容易猜到你有多少用戶,也容易通過(guò) ID 遍歷數(shù)據(jù)(比如從 1 開(kāi)始,依次訪問(wèn) /user/1、/user/2)。

但對(duì)于小項(xiàng)目 / 單機(jī)項(xiàng)目來(lái)說(shuō),這些缺點(diǎn)根本不是問(wèn)題。比如你做一個(gè)企業(yè)內(nèi)部的管理系統(tǒng),就一臺(tái) MySQL 服務(wù)器,用戶量也就幾千人,用自增 ID 完全沒(méi)問(wèn)題,簡(jiǎn)單又高效。

方案 2:分布式項(xiàng)目,數(shù)據(jù)庫(kù)分段自增 ID 更靠譜

要是你的項(xiàng)目是分布式的,需要多臺(tái) MySQL 服務(wù)器(比如分庫(kù)分表),自增 ID 就不夠用了,這時(shí)候可以用 “數(shù)據(jù)庫(kù)分段自增 ID”。

啥是數(shù)據(jù)庫(kù)分段自增 ID?簡(jiǎn)單說(shuō)就是:專(zhuān)門(mén)建一個(gè) “ID 生成器” 數(shù)據(jù)庫(kù),里面有一張表,記錄每個(gè)業(yè)務(wù)表的 ID 當(dāng)前最大值和步長(zhǎng),每次應(yīng)用需要生成 ID 的時(shí)候,就去這個(gè)表拿一段 ID(比如拿 1000 個(gè)),然后在應(yīng)用里自己慢慢用,用完了再去拿下一段。

舉個(gè)例子,比如用戶表的 ID:

  • 先建一個(gè) ID 生成器表:
CREATE TABLE id_generator (
    table_name VARCHAR(50) NOT NULL COMMENT '業(yè)務(wù)表名',
    current_max_id BIGINT NOT NULL COMMENT '當(dāng)前最大ID',
    step INT NOT NULL COMMENT '步長(zhǎng)',
    PRIMARY KEY (table_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'ID生成器表';
  • 初始化用戶表的 ID 配置:
INSERT INTO id_generator (table_name, current_max_id, step) VALUES ('user', 0, 1000);
  • 應(yīng)用需要生成用戶 ID 的時(shí)候,先執(zhí)行以下 SQL,拿一段 ID(0-999):
UPDATE id_generator 
SET current_max_id = current_max_id + step 
WHERE table_name = 'user' 
AND current_max_id = 0;
  • 應(yīng)用拿到這段 ID 后,就可以從 0 開(kāi)始,依次生成 0、1、2……999,用完了再去拿下一段(1000-1999)。

這種方案的優(yōu)點(diǎn):

  • 有序性:ID 是連續(xù)的,符合 MySQL 主鍵的要求,不會(huì)出現(xiàn)頁(yè)分裂。
  • 分布式唯一:因?yàn)樗袘?yīng)用都從同一個(gè) ID 生成器拿 ID,所以不會(huì)重復(fù)。
  • 性能好:每次拿一段 ID,不用每次生成 ID 都訪問(wèn)數(shù)據(jù)庫(kù),減少數(shù)據(jù)庫(kù)壓力。
  • 配置簡(jiǎn)單:不用依賴 ZooKeeper、Etcd 這些分布式協(xié)調(diào)工具,只需要一個(gè)數(shù)據(jù)庫(kù)就行。

缺點(diǎn)也有,就是 ID 生成器數(shù)據(jù)庫(kù)是單點(diǎn),要是這個(gè)數(shù)據(jù)庫(kù)掛了,所有需要生成 ID 的業(yè)務(wù)都得停。不過(guò)可以搞主從復(fù)制,主庫(kù)掛了就切從庫(kù),解決單點(diǎn)問(wèn)題。

我之前參與的一個(gè)電商項(xiàng)目,用的就是這種方案,分了 10 個(gè)庫(kù),每個(gè)庫(kù)有 10 個(gè)表,每天新增訂單 100 多萬(wàn),用數(shù)據(jù)庫(kù)分段自增 ID,從來(lái)沒(méi)出現(xiàn)過(guò) ID 重復(fù)或者性能問(wèn)題,特別穩(wěn)定。

方案 3:高并發(fā)場(chǎng)景,Redis 生成 ID 也不錯(cuò)

要是你的項(xiàng)目并發(fā)特別高,比如秒殺活動(dòng),每秒要生成幾萬(wàn)甚至幾十萬(wàn)的 ID,數(shù)據(jù)庫(kù)分段自增 ID 可能會(huì)有點(diǎn)吃力(因?yàn)槊看文枚?ID 都要訪問(wèn)數(shù)據(jù)庫(kù)),這時(shí)候可以用 Redis 生成 ID。

Redis 生成 ID 的原理很簡(jiǎn)單:利用 Redis 的 INCR 命令(原子性遞增),每次生成 ID 的時(shí)候,就調(diào)用 INCR 命令,讓某個(gè)鍵的值加 1,這個(gè)值就是新的 ID。

比如生成用戶 ID:

  • 先在 Redis 里設(shè)置一個(gè)鍵,初始值為 0:
SET user_id 0
  • 每次需要生成用戶 ID 的時(shí)候,調(diào)用 INCR 命令:
INCR user_id

這樣每次調(diào)用 INCR,都會(huì)返回一個(gè)唯一的、有序的 ID。為了提高性能,也可以像數(shù)據(jù)庫(kù)分段自增那樣,一次性從 Redis 拿一段 ID,比如拿 1000 個(gè):

INCRBY user_id 1000

這樣就能拿到一段 ID(比如從 1001 到 2000),然后在應(yīng)用里自己慢慢用。Redis 生成 ID 的優(yōu)點(diǎn):

  • 性能極高:Redis 是內(nèi)存數(shù)據(jù)庫(kù),INCR 命令的性能特別好,每秒能處理幾十萬(wàn)次請(qǐng)求,完全能滿足高并發(fā)場(chǎng)景。
  • 有序性:ID 是連續(xù)遞增的,符合 MySQL 主鍵要求。
  • 分布式唯一:所有應(yīng)用都訪問(wèn)同一個(gè) Redis,不會(huì)出現(xiàn) ID 重復(fù)。

缺點(diǎn):

  • 需要依賴 Redis:要是 Redis 掛了,ID 生成就會(huì)出問(wèn)題,所以得搞 Redis 集群,保證高可用。
  • 數(shù)據(jù)持久化問(wèn)題:要是 Redis 沒(méi)有持久化,或者持久化失敗,Redis 重啟后,ID 會(huì)從之前的值開(kāi)始,可能會(huì)重復(fù)。所以需要開(kāi)啟 Redis 的 AOF 持久化,并且配置合適的持久化策略。

這種方案適合高并發(fā)場(chǎng)景,比如秒殺、直播帶貨這些需要快速生成大量 ID 的業(yè)務(wù)。我之前做過(guò)一個(gè)秒殺項(xiàng)目,每秒并發(fā) 10 萬(wàn) +,用 Redis 生成訂單 ID,特別穩(wěn)定,從來(lái)沒(méi)掉過(guò)鏈子。

總結(jié):別再盲目跟風(fēng),選對(duì)主鍵才是王道

看到這里,你應(yīng)該明白為啥我用雪花 ID 當(dāng) MySQL 主鍵會(huì)被領(lǐng)導(dǎo)懟了吧?不是雪花 ID 不好,也不是 UUID 不好,而是它們不適合當(dāng) MySQL 主鍵。

選 MySQL 主鍵,就像選鞋子,不是越貴越好,也不是越高級(jí)越好,而是要合腳??偨Y(jié)一下:

  • 小項(xiàng)目 / 單機(jī)項(xiàng)目:直接用自增 ID,簡(jiǎn)單高效,不用瞎折騰。
  • 分布式項(xiàng)目(中低并發(fā)):用數(shù)據(jù)庫(kù)分段自增 ID,穩(wěn)定可靠,配置簡(jiǎn)單。
  • 分布式項(xiàng)目(高并發(fā)):用 Redis 生成 ID,性能極高,能扛住大流量。
  • 要是你實(shí)在想用雪花 ID:那一定要做好時(shí)鐘回?fù)芴幚砗蜋C(jī)器 ID 配置,并且接受它可能帶來(lái)的遷移麻煩和排序問(wèn)題。
  • 至于 UUID:除非你腦子進(jìn)水了,否則別把它當(dāng) MySQL 主鍵。

技術(shù)沒(méi)有好壞之分,只有合適不合適。別看到別人用雪花 ID、用 UUID,你就跟著用,得先搞明白背后的原理,結(jié)合自己的業(yè)務(wù)場(chǎng)景,才能做出正確的選擇。不然哪天被領(lǐng)導(dǎo)懟了,還不知道為啥,多冤啊!


責(zé)任編輯:武曉燕 來(lái)源: 石杉的架構(gòu)筆記
相關(guān)推薦

2021-04-12 07:32:01

數(shù)據(jù)庫(kù)

2020-08-31 11:20:53

MySQLuuidid

2023-08-28 12:07:06

UUIDMySQL

2025-07-28 01:22:00

2020-09-08 09:04:26

uuidMySQL主鍵

2024-05-29 09:05:17

2025-07-03 02:15:00

MySQLID+UUIDB+樹(shù)

2024-12-25 15:32:29

2023-01-30 09:16:58

MySQL雪花算法

2025-05-15 03:00:00

2025-08-06 09:31:12

2025-09-16 07:00:00

雪花算法IDPython

2024-10-24 09:22:30

2025-06-04 02:15:00

2021-03-03 08:29:54

MySQL查詢優(yōu)化

2021-01-12 09:24:24

Apple ID蘋(píng)果鎖定

2019-11-21 16:01:15

跳槽那些事兒人生第一份工作網(wǎng)易

2019-09-05 13:06:08

雪花算法分布式ID

2024-12-04 08:38:29

2015-02-13 10:42:31

前端工具Dreamweaver
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)