日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

mybatisplus新增返回主键_第17期:索引设计(主键设计)

發(fā)布時(shí)間:2024/7/23 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mybatisplus新增返回主键_第17期:索引设计(主键设计) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

表的主鍵指的針對一張表中的一列或者多列,其結(jié)果必須能標(biāo)識表中每行記錄的唯一性。InnoDB 表是索引組織表,主鍵既是數(shù)據(jù)也是索引。

主鍵的設(shè)計(jì)原則

1. 對空間占用要小上一篇我們介紹過 InnoDB 主鍵的存儲方式,主鍵占用空間越小,每個(gè)索引頁里存放的鍵值越多,這樣一次性放入內(nèi)存的數(shù)據(jù)也就越多。

2. 最好是有一定的排序?qū)傩匀?INT32 類型來做主鍵,數(shù)值有嚴(yán)格的排序,那新記錄的插入只要往原先數(shù)據(jù)頁后面添加新記錄或者在數(shù)據(jù)頁后新增空頁來填充記錄即可,這樣有嚴(yán)格排序的主鍵寫入速度也會(huì)非常快。

3. 數(shù)據(jù)類型為整形數(shù)據(jù)類型早就已經(jīng)講過,按照前兩點(diǎn)的需求,最理想的當(dāng)然是選擇整數(shù)類型,比如 int32 unsigned。數(shù)據(jù)順序增長,要么是數(shù)據(jù)庫自己生成,要么是業(yè)務(wù)自動(dòng)生成。

一、與業(yè)務(wù)無關(guān)的屬性做主鍵

1.1 自增字段做主鍵

這是 MySQL 最推薦的方式。一般用 INT32 可以滿足大部分場景,單庫單表可以最大保存 42 億行記錄;含有自增字段的新增記錄會(huì)順序添加到當(dāng)前索引節(jié)點(diǎn)的后續(xù)位置直到數(shù)據(jù)頁寫滿為止,再寫新頁。這樣會(huì)極大程度的減少數(shù)據(jù)頁的隨機(jī) IO。

用自增字段做主鍵可能需要注意兩個(gè)問題:

第一個(gè)問題:MySQL 原生自增鍵拆分

如果隨著數(shù)據(jù)后期增長,有拆庫拆表預(yù)期,可以考慮用 INT64;MySQL 原生支持拆庫拆表的自增主鍵,通過自增步長與起始值來確定。最少要有 2 個(gè) MySQL 節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)自增步長為 2,假設(shè) server_id 分別為 1,2,那自增起始值也可以是 1,2。假設(shè)下面是第 1 個(gè) MySQL 節(jié)點(diǎn),設(shè)置好了步長和起始值后,表 tmp 插入三行,每行嚴(yán)格按照設(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é)點(diǎn) 2 的值,也能成功插入,MySQL 默認(rèn)對這塊沒有什么約束,最好是數(shù)據(jù)入庫前就校驗(yàn)好。

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)

第二個(gè)問題:MySQL 自增鍵合并

這個(gè)問題一般牽扯到老的系統(tǒng)改造升級,比如多個(gè)分部老系統(tǒng)數(shù)據(jù)要向新系統(tǒng)合并,那之前每個(gè)分部的自增主鍵不能簡單的合并,可能會(huì)有主鍵沖突。舉個(gè)例子,假設(shè)武漢市每個(gè)區(qū)都有自己的醫(yī)保數(shù)據(jù),并且以前每個(gè)區(qū)都是自己獨(dú)立設(shè)計(jì)的數(shù)據(jù)庫,現(xiàn)在醫(yī)保要升級為全市統(tǒng)一,以市為單位設(shè)計(jì)新的數(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)

由于之前兩個(gè)區(qū)數(shù)據(jù)庫設(shè)計(jì)的人都沒有考慮以后合并的事情,所以每個(gè)區(qū)的表都有自己獨(dú)立的自增主鍵,考慮這樣建立一張匯總表 n3,有新的自增 ID,并且設(shè)計(jì)導(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)

這樣進(jìn)行匯總, 應(yīng)用代碼可能不太確定怎么連接老的數(shù)據(jù),這張表缺少一個(gè) old_id 到原始表名的映射。那基于原始表 ID 與原始表名的映射關(guān)系建立一個(gè)多值索引。比如以下例子:

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,建立一個(gè)包含新的自增字段主鍵,原來表 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) );

當(dāng)然,關(guān)于數(shù)據(jù)匯總遷移的話題,討論篇幅太長,不在本節(jié)范圍。

1.2 UUID 做主鍵

UUID 和自增主鍵一樣,能保證主鍵的唯一性。但是天生無序、隨機(jī)產(chǎn)生、占用空間大。在 MySQL 里,用 char(36) 來存儲 UUID,沒有專門的 UUID 數(shù)據(jù)類型,類似這樣的字符串: ‘7985847c-7d59-11ea-8add-080027c52750’。由于 InnoDB 表的特性,應(yīng)該避免用 char(36) 保存原始 UUID 的方式做表主鍵。

雖然 UUID 無序,且存在空間浪費(fèi),但天生隨機(jī)這個(gè)優(yōu)點(diǎn)能否利用上?

MySQL 提供了以下的優(yōu)化方法來讓原始 UUID 可以被用于表主鍵:

函數(shù) uuid_to_bin

MySQL 提供了函數(shù) uuid_to_bin,把 UUID 字符串變?yōu)?16 個(gè)字節(jié)的二進(jìn)制串。類似于某些數(shù)據(jù)庫(比如 POSTGRESQL)的 UUID 類型。函數(shù) uuid_to_bin 返回?cái)?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 還提供了一個(gè)函數(shù) uuid_short,用來生成類似 UUID 的全局 ID,結(jié)果為 INT64。具體計(jì)算方式如下:

(server_id & 255) << 56 + (server_startup_time_in_seconds << 24) + incremented_variable++;

  • server_id & 255:占 1 個(gè)字節(jié);
  • server_startup_time_in_seconds:占 4 個(gè)字節(jié);
  • incremented_variable: 占 3 個(gè)字節(jié)。

如果滿足以下條件,那這個(gè)值就必定是唯一的

1. server_id 唯一并且對函數(shù) uuid_short() 的調(diào)用次數(shù)不超過每秒 16777216 次,也就是 2^24。所以一般情況下,uuid_short 函數(shù)能保證結(jié)果唯一。

2. uuid_short 函數(shù)生成的 ID 只需一個(gè)輕量級的 mutex 來保護(hù),這點(diǎn)比自增 ID 需要的 auto-inc 表鎖更省資源,生成結(jié)果肯定更加快速。

下面表 t_uuid_short 演示了如何用這個(gè)函數(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 的一個(gè)補(bǔ)充優(yōu)化,如果每秒調(diào)用次數(shù)少于 16777216,推薦用 uuid_short,而非自增 ID。

說了那么多,還是簡單驗(yàn)證下上面的結(jié)論,做個(gè)小實(shí)驗(yàn)。

以下實(shí)驗(yàn)涉及到四張表:

  • 新建 t_uuid: uuid 為主鍵
  • 表 t_binary:varbinary(16) 為主鍵
  • 表 t_uuid_short:bigint 為主鍵
  • 新建表 t_id:自增 ID 為主鍵

正如之前的預(yù)期,寫性能差異按從最差到最好排列依次為:t_uuid; t_binary;t_id;t_uuid_short。我們來實(shí)驗(yàn)下是否和預(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)

簡單寫了一個(gè)存儲過程,分別給這些表造 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 時(shí)間最長,t_uuid_short 時(shí)間最短。

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è)計(jì)要求可讀性很強(qiáng),類似學(xué)生學(xué)號(入學(xué)年份+所屬系+所讀專業(yè)),購物訂單編碼等。其實(shí)非常不建議主鍵用這樣有實(shí)際意義的業(yè)務(wù)字段。可以新建一個(gè)自增主鍵或者 uuid_short() 函數(shù)字段,實(shí)際業(yè)務(wù)字段非主鍵設(shè)計(jì),變?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)錯(cuò)誤,比如可能由于老師原因錄入錯(cuò)誤數(shù)據(jù),或者是業(yè)務(wù)系統(tǒng)的 BUG 導(dǎo)致錄入數(shù)據(jù)有誤, 那不僅要對錄入表的主鍵做更改(這可是聚簇索引),還要更改依賴這張表的所有子表,這其實(shí)是一個(gè)很大的工程。但是如果有與業(yè)務(wù)不相關(guān)的主鍵,只需要更改業(yè)務(wù)字段(二級索引)就可以,不需要更改依賴這張表的子表。

關(guān)于 MySQL 主鍵的設(shè)計(jì)思路大致介紹到此,有問題歡迎留言,歡迎指正本篇任何不足之處。


關(guān)于 MySQL 的技術(shù)內(nèi)容,你們還有什么想知道的嗎?趕緊留言告訴小編吧!

總結(jié)

以上是生活随笔為你收集整理的mybatisplus新增返回主键_第17期:索引设计(主键设计)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。