共計 4746 個字符,預計需要花費 12 分鐘才能閱讀完成。
今天丸趣 TV 小編給大家分享一下 MySQL 為什么不能用 uuid 做主鍵的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
前言
在 mysql 中設計表的時候,mysql 官方推薦不要使用 uuid 或者不連續不重復的雪花 id(long 形且唯一,單機遞增), 而是推薦連續自增的主鍵 id, 官方的推薦是 auto_increment, 那么為什么不建議采用 uuid, 使用 uuid 究竟有什么壞處?
一、mysql 和程序實例
1.1. 要說明這個問題, 我們首先來建立三張表
分別是 user_auto_key,user_uuid,user_random_key, 分別表示自動增長的主鍵,uuid 作為主鍵,
隨機 key 作為主鍵, 其它我們完全保持不變.
根據控制變量法, 我們只把每個表的主鍵使用不同的策略生成, 而其他的字段完全一樣,然后測試一下表的插入速度和查詢速度:
注:這里的隨機 key 其實是指用雪花算法算出來的前后不連續不重復無規律的 id: 一串 18 位長度的 long 值
1.2. 光有理論不行, 直接上程序, 使用 spring 的 jdbcTemplate 來實現增查測試:
技術框架:springboot+jdbcTemplate+junit+hutool, 程序的原理就是連接自己的測試數據庫, 然后在相同的環境下寫入同等數量的數據,來分析一下 insert 插入的時間來進行綜合其效率,為了做到最真實的效果, 所有的數據采用隨機生成,比如名字、郵箱、地址都是隨機生成。
package com.wyq.mysqldemo;
import cn.hutool.core.collection.CollectionUtil;
import com.wyq.mysqldemo.databaseobject.UserKeyAuto;
import com.wyq.mysqldemo.databaseobject.UserKeyRandom;
import com.wyq.mysqldemo.databaseobject.UserKeyUUID;
import com.wyq.mysqldemo.diffkeytest.AutoKeyTableService;
import com.wyq.mysqldemo.diffkeytest.RandomKeyTableService;
import com.wyq.mysqldemo.diffkeytest.UUIDKeyTableService;
import com.wyq.mysqldemo.util.JdbcTemplateService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;
import java.util.List;
@SpringBootTest
class MysqlDemoApplicationTests {
@Autowired
private JdbcTemplateService jdbcTemplateService;
@Autowired
private AutoKeyTableService autoKeyTableService;
@Autowired
private UUIDKeyTableService uuidKeyTableService;
@Autowired
private RandomKeyTableService randomKeyTableService;
@Test
void testDBTime() {
StopWatch stopwatch = new StopWatch( 執行 sql 時間消耗
/**
* auto_increment key 任務
*/
final String insertSql = INSERT INTO user_key_auto(user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?)
List UserKeyAuto insertData = autoKeyTableService.getInsertData();
stopwatch.start( 自動生成 key 表任務開始
long start1 = System.currentTimeMillis();
if (CollectionUtil.isNotEmpty(insertData)) { boolean insertResult = jdbcTemplateService.insert(insertSql, insertData, false);
System.out.println(insertResult);
}
long end1 = System.currentTimeMillis();
System.out.println(auto key 消耗的時間: + (end1 - start1));
stopwatch.stop();
/**
* uudID 的 key
*/
final String insertSql2 = INSERT INTO user_uuid(id,user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?,?)
List UserKeyUUID insertData2 = uuidKeyTableService.getInsertData();
stopwatch.start( UUID 的 key 表任務開始
long begin = System.currentTimeMillis();
if (CollectionUtil.isNotEmpty(insertData)) { boolean insertResult = jdbcTemplateService.insert(insertSql2, insertData2, true);
System.out.println(insertResult);
}
long over = System.currentTimeMillis();
System.out.println(UUID key 消耗的時間: + (over - begin));
stopwatch.stop();
/**
* 隨機的 long 值 key
*/
final String insertSql3 = INSERT INTO user_random_key(id,user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?,?)
List UserKeyRandom insertData3 = randomKeyTableService.getInsertData();
stopwatch.start( 隨機的 long 值 key 表任務開始
Long start = System.currentTimeMillis();
if (CollectionUtil.isNotEmpty(insertData)) { boolean insertResult = jdbcTemplateService.insert(insertSql3, insertData3, true);
System.out.println(insertResult);
}
Long end = System.currentTimeMillis();
System.out.println(隨機 key 任務消耗時間: + (end - start));
stopwatch.stop();
String result = stopwatch.prettyPrint();
System.out.println(result);
}
1.3. 程序寫入結果
可以看出在數據量 100W 左右的時候,uuid 的插入效率墊底,并且在后序增加了 130W 的數據,uudi 的時間又直線下降。
時間占用量總體可以打出的效率排名為:auto_key random_key uuid,uuid 的效率最低,在數據量較大的情況下,效率直線下滑。那么為什么會出現這樣的現象呢?帶著疑問, 我們來探討一下這個問題:
二、使用 uuid 和自增 id 的索引結構對比
2.1. 使用自增 id 的內部結構
自增的主鍵的值是順序的, 所以 Innodb 把每一條記錄都存儲在一條記錄的后面。當達到頁面的最大填充因子時候(innodb 默認的最大填充因子是頁大小的 15/16, 會留出 1 /16 的空間留作以后的 修改):
①下一條記錄就會寫入新的頁中,一旦數據按照這種順序的方式加載,主鍵頁就會近乎于順序的記錄填滿,提升了頁面的最大填充率,不會有頁的浪費
②新插入的行一定會在原有的最大數據行下一行,mysql 定位和尋址很快,不會為計算新行的位置而做出額外的消耗
③減少了頁分裂和碎片的產生
2.2. 使用 uuid 的索引內部結構
因為 uuid 相對順序的自增 id 來說是毫無規律可言的, 新行的值不一定要比之前的主鍵的值要大, 所以 innodb 無法做到總是把新行插入到索引的最后, 而是需要為新行尋找新的合適的位置從而來分配新的空間。
這個過程需要做很多額外的操作,數據的毫無順序會導致數據分布散亂,將會導致以下的問題:
①寫入的目標頁很可能已經刷新到磁盤上并且從緩存上移除,或者還沒有被加載到緩存中,innodb 在插入之前不得不先找到并從磁盤讀取目標頁到內存中,這將導致大量的隨機 IO
②因為寫入是亂序的,innodb 不得不頻繁的做頁分裂操作, 以便為新的行分配空間, 頁分裂導致移動大量的數據,一次插入最少需要修改三個頁以上
③由于頻繁的頁分裂,頁會變得稀疏并被不規則的填充,最終會導致數據會有碎片
在把隨機值(uuid 和雪花 id)載入到聚簇索引 (innodb 默認的索引類型) 以后, 有時候會需要做一次 OPTIMEIZE TABLE 來重建表并優化頁的填充,這將又需要一定的時間消耗。
結論:使用 innodb 應該盡可能的按主鍵的自增順序插入,并且盡可能使用單調的增加的聚簇鍵的值來插入新行
2.3. 使用自增 id 的缺點
那么使用自增的 id 就完全沒有壞處了嗎?并不是,自增 id 也會存在以下幾點問題:
①別人一旦爬取你的數據庫, 就可以根據數據庫的自增 id 獲取到你的業務增長信息,很容易分析出你的經營情況
②對于高并發的負載,innodb 在按主鍵進行插入的時候會造成明顯的鎖爭用,主鍵的上界會成為爭搶的熱點,因為所有的插入都發生在這里,并發插入會導致間隙鎖競爭
③Auto_Increment 鎖機制會造成自增鎖的搶奪, 有一定的性能損失
附:Auto_increment 的鎖爭搶問題,如果要改善需要調優 innodb_autoinc_lock_mode 的配置
以上就是“MySQL 為什么不能用 uuid 做主鍵”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,丸趣 TV 小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注丸趣 TV 行業資訊頻道。