mybatisplus新增返回主键_第17期:索引设计(主键设计)
表的主鍵指的針對一張表中的一列或者多列,其結(jié)果必須能標識表中每行記錄的唯一性。InnoDB 表是索引組織表,主鍵既是數(shù)據(jù)也是索引。
主鍵的設(shè)計原則
1. 對空間占用要小上一篇我們介紹過 InnoDB 主鍵的存儲方式,主鍵占用空間越小,每個索引頁里存放的鍵值越多,這樣一次性放入內(nèi)存的數(shù)據(jù)也就越多。
2. 最好是有一定的排序?qū)傩匀?INT32 類型來做主鍵,數(shù)值有嚴格的排序,那新記錄的插入只要往原先數(shù)據(jù)頁后面添加新記錄或者在數(shù)據(jù)頁后新增空頁來填充記錄即可,這樣有嚴格排序的主鍵寫入速度也會非常快。
3. 數(shù)據(jù)類型為整形數(shù)據(jù)類型早就已經(jīng)講過,按照前兩點的需求,最理想的當然是選擇整數(shù)類型,比如 int32 unsigned。數(shù)據(jù)順序增長,要么是數(shù)據(jù)庫自己生成,要么是業(yè)務(wù)自動生成。
一、與業(yè)務(wù)無關(guān)的屬性做主鍵
1.1 自增字段做主鍵
這是 MySQL 最推薦的方式。一般用 INT32 可以滿足大部分場景,單庫單表可以最大保存 42 億行記錄;含有自增字段的新增記錄會順序添加到當前索引節(jié)點的后續(xù)位置直到數(shù)據(jù)頁寫滿為止,再寫新頁。這樣會極大程度的減少數(shù)據(jù)頁的隨機 IO。
用自增字段做主鍵可能需要注意兩個問題:
第一個問題:MySQL 原生自增鍵拆分
如果隨著數(shù)據(jù)后期增長,有拆庫拆表預(yù)期,可以考慮用 INT64;MySQL 原生支持拆庫拆表的自增主鍵,通過自增步長與起始值來確定。最少要有 2 個 MySQL 節(jié)點,每個節(jié)點自增步長為 2,假設(shè) server_id 分別為 1,2,那自增起始值也可以是 1,2。假設(shè)下面是第 1 個 MySQL 節(jié)點,設(shè)置好了步長和起始值后,表 tmp 插入三行,每行嚴格按照設(shè)置的方式插入數(shù)據(jù)。
mysql> set @@auto_increment_increment=2; Query OK, 0 rows affected (0.00 sec)mysql> set @@auto_increment_offset=1; Query OK, 0 rows affected (0.00 sec)mysql> insert into tmp values(null),(null),(null); Query OK, 3 rows affected (0.01 sec) Records: 3 Duplicates: 0 Warnings: 0mysql> select * from tmp; +----+ | id | +----+ | 1 | | 3 | | 5 | +----+ 3 rows in set (0.00 sec)但是這塊 MySQL 并不能保證其他的值不沖突,比如插入一條節(jié)點 2 的值,也能成功插入,MySQL 默認對這塊沒有什么約束,最好是數(shù)據(jù)入庫前就校驗好。
mysql> insert into tmp values(2); Query OK, 1 row affected (0.02 sec)mysql> select * from tmp; +----+ | id | +----+ | 1 | | 2 | | 3 | | 5 | +----+ 4 rows in set (0.00 sec)第二個問題:MySQL 自增鍵合并
這個問題一般牽扯到老的系統(tǒng)改造升級,比如多個分部老系統(tǒng)數(shù)據(jù)要向新系統(tǒng)合并,那之前每個分部的自增主鍵不能簡單的合并,可能會有主鍵沖突。舉個例子,假設(shè)武漢市每個區(qū)都有自己的醫(yī)保數(shù)據(jù),并且以前每個區(qū)都是自己獨立設(shè)計的數(shù)據(jù)庫,現(xiàn)在醫(yī)保要升級為全市統(tǒng)一,以市為單位設(shè)計新的數(shù)據(jù)庫模型。
武昌的數(shù)據(jù)如下,對應(yīng)表 n1,
mysql> select * from n1; +----+ | id | +----+ | 1 | | 2 | | 3 | +----+ 3 rows in set (0.00 sec)漢陽的數(shù)據(jù)如下,對應(yīng)表 n2,
mysql> select * from n2; +----+ | id | +----+ | 1 | | 2 | | 3 | +----+ 3 rows in set (0.00 sec)由于之前兩個區(qū)數(shù)據(jù)庫設(shè)計的人都沒有考慮以后合并的事情,所以每個區(qū)的表都有自己獨立的自增主鍵,考慮這樣建立一張匯總表 n3,有新的自增 ID,并且設(shè)計導(dǎo)入老系統(tǒng)的 ID。
mysql> create table n3 (id int auto_increment primary key, old_id int); Query OK, 0 rows affected (0.07 sec) mysql> insert into n3 (old_id) select * from n1 union all select * from n2; Query OK, 6 rows affected (0.01 sec) Records: 6 Duplicates: 0 Warnings: 0mysql> select * from n3; +----+--------+ | id | old_id | +----+--------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | | 4 | 1 | | 5 | 2 | | 6 | 3 | +----+--------+ 6 rows in set (0.00 sec)這樣進行匯總, 應(yīng)用代碼可能不太確定怎么連接老的數(shù)據(jù),這張表缺少一個 old_id 到原始表名的映射。那基于原始表 ID 與原始表名的映射關(guān)系建立一個多值索引。比如以下例子:
mysql> create table n4(old_id int, old_name varchar(64),primary key(old_id,old_name)); Query OK, 0 rows affected (0.05 sec)mysql> insert into n4 select id ,'n1' from n1 union all select id,'n2' from n2; Query OK, 6 rows affected (0.02 sec) Records: 6 Duplicates: 0 Warnings: 0mysql> select * from n4; +--------+----------+ | old_id | old_name | +--------+----------+ | 1 | n1 | | 1 | n2 | | 2 | n1 | | 2 | n2 | | 3 | n1 | | 3 | n2 | +--------+----------+ 6 rows in set (0.00 sec)最終表結(jié)構(gòu),結(jié)合前面兩張表 n3 和 n4,建立一個包含新的自增字段主鍵,原來表 ID,原來表名的新表:
create table n5( id int unsigned auto_increment primary key, old_id int, old_name varchar(64), unique key udx_old_id_old_name (old_id,old_name) );當然,關(guān)于數(shù)據(jù)匯總遷移的話題,討論篇幅太長,不在本節(jié)范圍。
1.2 UUID 做主鍵
UUID 和自增主鍵一樣,能保證主鍵的唯一性。但是天生無序、隨機產(chǎn)生、占用空間大。在 MySQL 里,用 char(36) 來存儲 UUID,沒有專門的 UUID 數(shù)據(jù)類型,類似這樣的字符串: ‘7985847c-7d59-11ea-8add-080027c52750’。由于 InnoDB 表的特性,應(yīng)該避免用 char(36) 保存原始 UUID 的方式做表主鍵。
雖然 UUID 無序,且存在空間浪費,但天生隨機這個優(yōu)點能否利用上?
MySQL 提供了以下的優(yōu)化方法來讓原始 UUID 可以被用于表主鍵:
函數(shù) uuid_to_bin
MySQL 提供了函數(shù) uuid_to_bin,把 UUID 字符串變?yōu)?16 個字節(jié)的二進制串。類似于某些數(shù)據(jù)庫(比如 POSTGRESQL)的 UUID 類型。函數(shù) uuid_to_bin 返回數(shù)據(jù)類型為 varbinary(16)。
例如表 t_binary,
mysql> create table t_binary(id varbinary(16) primary key,r1 int, key idx_r1(r1)); Query OK, 0 rows affected (0.07 sec)mysql> insert into t_binary values (uuid_to_bin(uuid()),1),(uuid_to_bin(uuid()),2); Query OK, 2 rows affected (0.01 sec) Records: 2 Duplicates: 0 Warnings: 0mysql> select * from t_binary; +------------------------------------+------+ | id | r1 | +------------------------------------+------+ | 0x412234A77DEF11EA9AF9080027C52750 | 1 | | 0x412236E27DEF11EA9AF9080027C52750 | 2 | +------------------------------------+------+ 2 rows in set (0.00 sec)函數(shù) uuid_short
varbinary(16) 依然是無序的,為此 MySQL 還提供了一個函數(shù) uuid_short,用來生成類似 UUID 的全局 ID,結(jié)果為 INT64。具體計算方式如下:
(server_id & 255) << 56 + (server_startup_time_in_seconds << 24) + incremented_variable++;
- server_id & 255:占 1 個字節(jié);
- server_startup_time_in_seconds:占 4 個字節(jié);
- incremented_variable: 占 3 個字節(jié)。
如果滿足以下條件,那這個值就必定是唯一的
1. server_id 唯一并且對函數(shù) uuid_short() 的調(diào)用次數(shù)不超過每秒 16777216 次,也就是 2^24。所以一般情況下,uuid_short 函數(shù)能保證結(jié)果唯一。
2. uuid_short 函數(shù)生成的 ID 只需一個輕量級的 mutex 來保護,這點比自增 ID 需要的 auto-inc 表鎖更省資源,生成結(jié)果肯定更加快速。
下面表 t_uuid_short 演示了如何用這個函數(shù)。
mysql> create table t_uuid_short (id bigint unsigned primary key,r1 int, key idx_r1(r1)); Query OK, 0 rows affected (0.06 sec)mysql> insert into t_uuid_short values(uuid_short(),1),(uuid_short(),2) Query OK, 2 rows affected (0.02 sec) Records: 2 Duplicates: 0 Warnings: 0mysql> select * from t_uuid_short; +----------------------+------+ | id | r1 | +----------------------+------+ | 16743984358464946177 | 1 | | 16743984358464946178 | 2 | +----------------------+------+ 2 rows in set (0.00 sec)可以看到 uuid_short 生成的數(shù)據(jù)是基于 INT64 有序的,所以這塊可以看做是自增 ID 的一個補充優(yōu)化,如果每秒調(diào)用次數(shù)少于 16777216,推薦用 uuid_short,而非自增 ID。
說了那么多,還是簡單驗證下上面的結(jié)論,做個小實驗。
以下實驗涉及到四張表:
- 新建 t_uuid: uuid 為主鍵
- 表 t_binary:varbinary(16) 為主鍵
- 表 t_uuid_short:bigint 為主鍵
- 新建表 t_id:自增 ID 為主鍵
正如之前的預(yù)期,寫性能差異按從最差到最好排列依次為:t_uuid; t_binary;t_id;t_uuid_short。我們來實驗下是否和預(yù)期相符。
新增的兩張表結(jié)構(gòu):
mysql> create table t_uuid(id char(36) primary key, r1 int, key idx_r1(r1)); Query OK, 0 rows affected (0.06 sec)mysql> create table t_id (id bigint auto_increment primary key, r1 int, key idx_r1(r1)); Query OK, 0 rows affected (0.08 sec)簡單寫了一個存儲過程,分別給這些表造 30W 條記錄。
DELIMITER $$CREATEPROCEDURE `ytt`.`sp_insert_data`(f_tbname VARCHAR(64),f_number INT UNSIGNED)BEGINDECLARE i INT UNSIGNED DEFAULT 0; SET @@autocommit=0;IF f_tbname = 't_uuid' THENSET @stmt = CONCAT('insert into t_uuid values (uuid(),ceil(rand()*100));');ELSEIF f_tbname = 't_binary' THENSET @stmt = CONCAT('insert into t_binary values(uuid_to_bin(uuid()),ceil(rand()*100));');ELSEIF f_tbname = 't_uuid_short' THENSET @stmt = CONCAT('insert into t_uuid_short values(uuid_short(),ceil(rand()*100));');ELSEIF f_tbname = 't_id' THENSET @stmt = CONCAT('insert into t_id(r1) values(ceil(rand()*100));');END IF;WHILE i < f_numberDO PREPARE s1 FROM @stmt;EXECUTE s1;SET i = i + 1;IF MOD(i,50) = 0 THENCOMMIT;END IF;END WHILE;COMMIT;DROP PREPARE s1; SET @@autocommit=1;END$$DELIMITER ;接下來分別調(diào)用存儲過程,結(jié)果和預(yù)期一致。t_uuid 時間最長,t_uuid_short 時間最短。
mysql> call sp_insert_data('t_uuid',300000); Query OK, 0 rows affected (5 min 23.33 sec)mysql> call sp_insert_data('t_binary',300000); Query OK, 0 rows affected (4 min 48.92 sec)mysql> call sp_insert_data('t_id',300000); Query OK, 0 rows affected (3 min 40.38 sec)mysql> call sp_insert_data('t_uuid_short',300000); Query OK, 0 rows affected (3 min 9.94 sec)二、與業(yè)務(wù)有關(guān)的屬性做主鍵。
主鍵的設(shè)計要求可讀性很強,類似學(xué)生學(xué)號(入學(xué)年份+所屬系+所讀專業(yè)),購物訂單編碼等。其實非常不建議主鍵用這樣有實際意義的業(yè)務(wù)字段。可以新建一個自增主鍵或者 uuid_short() 函數(shù)字段,實際業(yè)務(wù)字段非主鍵設(shè)計,變?yōu)槠胀ㄎㄒ凰饕1热绫?n5:
mysql> create table n5(id int unsigned auto_increment primary key, userno int unsigned ,unique key udx_userno(userno)); Query OK, 0 rows affected (0.08 sec)用 userno(用戶編碼)來做主鍵,如果在業(yè)務(wù)端數(shù)據(jù)已經(jīng)錯誤,比如可能由于老師原因錄入錯誤數(shù)據(jù),或者是業(yè)務(wù)系統(tǒng)的 BUG 導(dǎo)致錄入數(shù)據(jù)有誤, 那不僅要對錄入表的主鍵做更改(這可是聚簇索引),還要更改依賴這張表的所有子表,這其實是一個很大的工程。但是如果有與業(yè)務(wù)不相關(guān)的主鍵,只需要更改業(yè)務(wù)字段(二級索引)就可以,不需要更改依賴這張表的子表。
關(guān)于 MySQL 主鍵的設(shè)計思路大致介紹到此,有問題歡迎留言,歡迎指正本篇任何不足之處。
關(guān)于 MySQL 的技術(shù)內(nèi)容,你們還有什么想知道的嗎?趕緊留言告訴小編吧!
總結(jié)
以上是生活随笔為你收集整理的mybatisplus新增返回主键_第17期:索引设计(主键设计)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: webpack打包后的文件夹是空的_vu
- 下一篇: 以实际产品为例, 进行软件工程训练的作业