1. 程式人生 > >Mysql:小主鍵,大問題

Mysql:小主鍵,大問題

今日格言:讓一切迴歸原點,迴歸最初的為什麼。

本篇講解 Mysql 的主鍵問題,從為什麼的角度來了解 Mysql 主鍵相關的知識,並拓展到主鍵的生成方案問題。再也不怕被問到 Mysql 時只知道 CRUD 了。

一、為什麼需要主鍵

  1. 資料記錄需具有唯一性(第一正規化)
  2. 資料需要關聯 join
  3. 資料庫底層索引用於檢索資料所需

以下廢話連篇,可以直接跳過到下一節。

“資訊是用來消除隨機不定性的東西”(夏農)。人通過獲得、識別自然界和社會的不同資訊來區別不同事物,得以認識和改造世界。資料是反映客觀事物屬性的記錄,是資訊的具體表現形式。資料經過加工處理之後,就成為資訊;而資訊需要經過數字化轉變成資料才能儲存和傳輸。資料庫就是用於儲存資料記錄的。既已如此,記錄便是具有確定性(相對)的資訊,其確定性即唯一性。我們得出第一條原因:

1.資料記錄需具有唯一性

世界是由客觀存在及其關係組成的。資料是數字化和模型化的存在關係。資料除了本身的描述價值外,其價值還在於其相互關聯性。為實現關聯的準確性,資料需要有對外相互關聯的標識。所以體現在資料儲存上,主鍵的第二作用,也是存在的第二因素即:

2.資料需要關聯

資料用於描述客觀實在的,本身沒有意義。只有在根據主觀需求組織之後,通過一定方式滿足人認識事物的過程才具有了意義。所以資料需要被檢索,被組織。則主鍵第三個作用:

3.資料庫底層索引用於檢索資料所需

二、為什麼主鍵不宜過長

這個問題的點在長上。那短比長有什麼優勢?(嘿嘿嘿,內涵)—— 短不佔空間。但這麼點磁碟空間相對整個資料量來說微不足道,而且我們一般不怎麼用到主鍵列。那麼原因應該在快上,而且和原始資料關係不大。以此自然得出和索引相關,而且和索引讀取相關。那麼為什麼長主鍵在索引中會影響效能?

上面是 Innodb 的索引資料結構。左邊是聚簇索引,通過主鍵定位資料記錄。右邊是二級索引,對列資料做索引,通過列資料查詢資料主鍵。如果通過二級索引查詢資料,流程如圖上所示,先從二級索引樹上搜索到主鍵,然後在聚簇索引上通過主鍵搜尋到資料行。其中二級索引的葉子節點是直接儲存的主鍵值,而不是主鍵指標。所以如果主鍵太長,一個二級索引樹所能儲存的索引記錄就會變少,這樣在有限的索引緩衝中,需要讀取磁碟的次數就會變多,所以效能就會下降。

三、為什麼建議使用自增 ID

InnoDB 使用聚簇索引,如上圖所示,資料記錄本身被存於主索引(一顆 B+Tree)的葉子節點上。這就要求同一個葉子節點內(大小為一個記憶體頁或磁碟頁)的各條資料記錄按主鍵順序存放,因此每當有一條新的記錄插入時,MySQL 會根據其主鍵將其插入適當的節點和位置,如果頁面達到裝載因子(InnoDB 預設為 15/16),則開闢一個新的頁(節點)。

如果表使用自增主鍵,那麼每次插入新的記錄,記錄就會順序新增到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁。這樣就會形成一個緊湊的索引結構,近似順序填滿。由於每次插入時也不需要移動已有資料,因此效率很高,也不會增加很多開銷在維護索引上,如下圖左側所示。否則由於每次插入主鍵的值近似於隨機,因此每次新記錄都要被插到現有索引頁的中間某個位置,MySQL 不得不為了將新記錄插到合適位置而移動資料,如下圖右側所示,這樣就造成了一定的開銷。由於此,Mysql 為維護索引可能需要頻繁的重新整理緩衝,增加了方法磁碟 IO 的次數,而且時常需要對索引結構進行重組織。

四、業務 Key VS 邏輯 Key

業務 Key,即使用具有業務意義的 id 作為 Key,比如使用訂單流水號作為訂單表的主鍵 Key。邏輯 Key,即無關業務的 Key,按某種規則生成 Key,如自增 Key。

業務 Key 的優點

  • Key 具有業務意義,在查詢時可以直接作為搜尋關鍵字使用
  • 不需要額外的列和索引空間
  • 可以減少一些 join 操作。

業務 Key 的缺點

  • 當業務發生變化時,有時需要變更主鍵
  • 涉及多列 Key 時比較難操作
  • 業務 Key 往往比較長,所佔空間更大,導致更大的磁碟 IO
  • 在 Key 確定前不能持久化資料,有時我們沒有在確定資料 Key 時,就想先新增一條記錄,之後再更新業務 Key
  • 設計一個兼具易用和效能的 Key 生成方案比較難

邏輯 Key 的優點

  • 不會因為業務的變動而需要修改 Key 邏輯
  • 操作簡單,且易於管理
  • 邏輯 Key 往往更小,效能更優
  • 邏輯 Key 更容易保證唯一性
  • 更易於優化

邏輯 Key 缺點

  • 查詢主鍵列和主鍵索引需要額外的磁碟空間
  • 在插入資料和更新資料時需要額外的 IO
  • 更多的 join 可能
  • 如果沒有唯一性策略限制,容易出現重複的 Key
  • 測試環境和正式環境 Key 不一致,不利於排查問題
  • Key 的值沒有和資料關聯,不符合三正規化
  • 不能用於搜尋關鍵字
  • 依賴不同資料庫系統的具體實現,不利於底層資料庫的替換

五、主鍵生成

一般情況下,我們都使用 Mysql 的自增 ID,來作為表的主鍵,這樣簡單,而且從上面講到的來看,效能也是最好的。但是在分庫分表的情況情況下,自增 ID 則不能滿足需求。我們可以來看看不同資料庫生成 ID 的方式,也看一些分散式 ID 生成方案。利於我們思考甚至實現自己的分散式 ID 生成服務。

資料庫的實現

Mysql 自增

Mysql 在記憶體中維護一個自增計數器,每次訪問 auto-increment 計數器的時候, InnoDB 都會加上一個名為AUTO-INC 鎖直到該語句結束(注意鎖只持有到語句結束,不是事務結束)。AUTO-INC 鎖是一個特殊的表級別的鎖,用來提升包含 auto_increment 列的併發插入性。

在分散式的情況下,其實可以獨立一個服務和資料庫來做 id 生成,依舊依賴 Mysql 的表 id 自增能力來為第三方服務統一生成 id。為效能考慮可以不同業務使用不同的表。

Mongodb ObjectId

Mongodb 為防止主鍵衝突,設計了一個 ObjectId 作為主鍵 id。它由一個 12 位元組的十六進位制數字組成,其中包含以下幾部分:

  1. Time:時間戳。4 位元組。秒級。

  2. Machine:機器標識。3 位元組。一般是機器主機名的雜湊值,這樣就確保了不同主機生成不同的機器 hash 值,確保在分散式中不造成衝突,同一臺機器的值相同。

  3. PID:程序 ID。2 位元組。上面的 Machine 是為了確保在不同機器產生的 objectId 不衝突,而 pid 就是為了在同一臺機器不同的 mongodb 程序產生的 objectId 不衝突。

  4. INC:自增計數器。3 位元組。前面的九個位元組保證了一秒內不同機器不同程序生成的 objectId 不衝突,自增計數器,用來確保在同一秒內產生的 objectId 也不會發現衝突,允許 256 的 3 次方等於 16777216 條記錄的唯一性。

Cassandra TimeUUID

Cassandra 使用下面規則生成一個唯一的 id:time + MAC + sequence

方案

  1. Zookeeper 自增:通過 zk 的自增機制實現。
  2. Redis 自增:通過 Redis 的自增機制實現。
  3. UUID:使用 UUID 字串作為 Key。
  4. snowflake 演算法:和 Mongodb 的實現類似,1位符號位 + 41位時間戳(毫秒級)+ 10位資料機器位 + 12位毫秒內的序列

開源實現

  1. 百度 UidGenerator:基於snowflake演算法。
  2. 美團 Leaf:同時實現了基於 Mysql 自增(優化)和 snowflake 演算法的機制。

推薦系列

列式儲存
時間序列資料庫(TSDB)初識與選擇
十分鐘瞭解 Apache Druid
Apache Druid 底層儲存設計
Apache Druid 的叢集設計與工作流程
Mysql 大表問題和解決

想了解更多資料儲存相關知識,請關注我的公眾號。

相關推薦

Mysql問題

今日格言:讓一切迴歸原點,迴歸最初的為什麼。 本篇講解 Mysql 的主鍵問題,從為什麼的角度來了解 Mysql 主鍵相關的知識,並拓展到主鍵的生成方案問題。再也不怕被問到 Mysql 時只知道 CRUD 了。 一、為什麼需要主鍵 資料記錄需具有唯一性(第一正規化) 資料需要關聯 join 資料庫底層索

mysql自增長刪除數據後順序重新排序

mar drop 排序 ews 字段 name key 博文 csdn 用數據庫的時候,難免會刪除數據,會發現設置的主鍵增長不是按照正常順序排列,中間有斷隔比如這樣。 以我這個情況舉例 處理方法的原理:刪除原有的自增ID,重新建立新的自增ID。 ALTER TABLE `n

MySQL聯合、索引

MySQL資料庫用聯合主鍵。用兩張表測試。 # 1分鐘資料表 CREATE TABLE md_1min2( `d_1min` DATETIME(3), `code` CHAR(16), `open` FLOAT, `high` FLOAT, `low` FLOAT, `c

MySQL—概念使用者的建立外來資料型別表格建立

MySQL   DBMS,MySQL的概念,資料庫分類,以前MySQL的部署中的一些概念 #DBMS:資料庫管理系統,用於管理資料庫的大型軟體。mysql就是dbms的一種 #Mysql:是用於管理檔案的一個軟體 #服務端軟體

程式碼自留地檔案合併成檔案需要配置BytesZip使用java

public class FileZip implements Serializable { String fileName = null; byte [] zipBytes = null;

MYSQL筆記2】複製表在已有表的基礎上設定insert和replace

之前我自己建立好了一個數據庫xscj;表xs是已經定義好的 具體的定義資料型別如下: 為了複製表xs,我們新建一個表名為xstext,使用下列語句進行復制xs,或者說是備份都可以; create table xstext as select * from x create ta

MYSQL筆記2】復制表在已有表的基礎上設置insert和replace

主鍵 insert 我們 key repl 圖片 prim 個數 insert語句 之前我自己建立好了一個數據庫xscj;表xs是已經定義好的 具體的定義數據類型如下: 為了復制表xs,我們新建一個表名為xstext,使用下列語句進行復制xs,或者說是備份都可以; c

MySql資料庫在表中新增新欄位設定設定外來欄位移動位置以及修改資料庫後如何進行部署和維護的總結

1,為當前已有的表新增新的欄位 alter table student add studentName varchar(20) not null; 2,為當前已有的表中的欄位設定為主鍵自增 alter table student add constraint PK_studentId primaryKe

mysql 更換新增新欄位及初始化賦值 sql

順序執行以下sql語句 1.刪除表主鍵約束 ALTER TABLE mobilems_m_weex DROP PRIMARY KEY; 2.新增新主鍵欄位id ALTER TABLE mobilems_m_weex ADD id BIGINT(20) NOT NULL C

MySQL入門——修改資料表4新增約束、顯示錶結構、新增唯一約束

這裡的新增主鍵約束、新增唯一約束,指的是列級約束,是在資料表建立以後再新增的。 例如,給user3表中的id欄位新增主鍵約束: ALTER TABLE user3 ADD PRIMARY KE

MySQL聯合複合區別&建立

聯合主鍵:  當兩個資料表形成的是多對多的關係,那麼需要通過兩個資料表的主鍵來組成聯合主鍵,就可以確定每個資料表的其中一條記錄了 例:  學生表:student  create table student( id mediumint auto_increment co

資料庫的幾個概念外來索引唯一索引

主鍵:       主鍵是資料表的唯一索引,比如學生表裡有學號和姓名,姓名可能有重名的,但學號確是唯一的,你要從學生表中搜索一條紀錄如查詢一個人,就只能根據學號去查詢,這才能找出唯一的一個,這就是主鍵;如:id int(10) not null primary key aut

MySql自增id插入失敗或刪除後再插入亂序問題

在對資料庫進行操作的時候,資料庫的表裡的id是自增的,當資料被刪除或者新增或者插入失敗時,id會一直增上去,變得很亂,不會按照順序,下面是兩種解決辦法: alter table tablename drop column id; alter table tablenam

mysql索引唯一索引聯合索引的區別

索引是一種特殊的檔案(InnoDB資料表上的索引是表空間的一個組成部分),它們包含著對資料表裡所有記錄的引用指標。 普通索引(由關鍵字KEY或INDEX定義的索引)的唯一任務是加快對資料的訪問速度。 普通索引允許被索引的資料列包含重複的值。如果能確定某個資料列將只包含彼此各

mysql 外來唯一性約束

為已經新增好的資料表新增外來鍵: 語法:alter table 表名 add constraint FK_ID foreign key(你的外來鍵欄位名) REFERENCES 外表表

JS 點選事件onclick點選物件區域顏色區域顏色改變

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <

從團隊管理視角看重複建設問題輪子造怡情造傷身全域性出發成就更好的你

在一定規模的軟體研發團隊內,經常出現的情況是對同一個問題領域,會有多個人或多個者團隊矇頭再重複做系統或方案來解決相同問題。 甚至,在一些團隊內,技術人員為了職位晉升,會通過重複建設相關的系統來展示其能力,併名其名曰面向晉升程式設計。 對於個人來說,重複造輪子其實是人之本性,特別是對於優秀的研發工程師來說,自己

一張表多個字段是另一張表的關聯查詢語句

left join phone where table 查詢語句 tab tin 主鍵 let CREATE TABLE `User`(   `Id` BIGINT AUTO_INCREMENT NOT NULL,   `Name` VARCHAR(10)

子表父表;一對多多對一;梳理。

梳理 一段 引用 cnblogs .com 課程 alt img 分享 這個每次搞明白後,過一段時間又亂了,這次總結下: 子表與父表:   父表:被引用的表。被引用列一定是父表的主鍵。     子表:引用父表的某一列作為外鍵。 一對多,多對一:一的一方一定是父表,多的一