[https://www.cnblogs.com/youngyan/p/13638608.html](https://www.cnblogs.com/youngyan/p/13638608.html)
【前言】
一般選擇mysql數據庫中的表中ID主鍵,有以下幾種方式:
* * 自增ID
* 雪花(snowflake)算法
* uuid隨機數
* redis生成ID
本文將會先介紹這幾種算法,然后進行對比,思考什么情況下來選擇ID主鍵的問題。
【算法介紹】
一、自增ID
創建表的時候設置id為自增。 語法為:auto\_increment
二、uuid隨機數
需要在程序中進行設置。它的標準型式包含32個16進制數字,以連字號分為五段,表現形式為8-4-4-4-12的32個字符.
[](javascript:void(0); "復制代碼")
~~~
import java.util.UUID;
public class UTest {
public static void main(String[] args) {
UUID uuid = UUID.randomUUID();
System.out.println(uuid);
}
}
~~~
[](javascript:void(0); "復制代碼")
三、雪花算法
雪花算法是Twitter開源的分布式ID生成算法。生成的ID是純數字且具有時間順序。

自增、有序、適合分布式場景,生成時不依賴于數據庫,完全在內存中生成,每秒能生成數百萬的自增 ID,存入數據庫中,索引效率高。
* * * 時間位:可以根據時間進行排序,有助于提高查詢速度。
* 機器 ID 位:適用于分布式環境下對多節點的各個節點進行標識,可以具體根據節點數和部署情況設計劃分機器位 10 位長度,如劃分5位表示進程位等。
* 序列號位:是一系列的自增id,可以支持同一節點同一毫秒生成多個 ID 序號,12 位的計數序列號支持每個節點每毫秒產生 4096 個 ID 序號
snowflake 算法可以根據項目情況以及自身需要進行一定的修改。
四、Redis生成ID
【對比分析】
生成算法優點缺點長度自增ID代碼簡單,數據遞增單點故障,需要DBA專業維護32UUID實現簡單,不占用帶寬無序、查詢慢、不適合簡歷索引遞增雪花算法低位趨勢遞增,性能高依賴于服務器時間18Redis自增無單點故障,性能高,遞增占用帶寬,Redis集群維護自定義
1、代碼時間消耗分析
[](javascript:void(0); "復制代碼")
~~~
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);
}
~~~
[](javascript:void(0); "復制代碼")

時間占用量總體可以打出的效率排名為:auto\_key>random\_key>uuid。
uuid 的效率最低,在數據量較大的情況下,效率直線下滑。那么為什么會出現這樣的現象呢?帶著疑問,我們來探討一下這個問題:
使用 uuid 和自增 id 的索引結構對比
自增的主鍵的值是順序的,所以 InnoDB 把每一條記錄都存儲在一條記錄的后面。
當達到頁面的最大填充因子時候(InnoDB 默認的最大填充因子是頁大小的 15/16,會留出 1/16 的空間留作以后的修改)。**①**下一條記錄就會寫入新的頁中,一旦數據按照這種順序的方式加載,主鍵頁就會近乎于順序的記錄填滿,提升了頁面的最大填充率,不會有頁的浪費。**②**新插入的行一定會在原有的最大數據行下一行,MySQL 定位和尋址很快,不會為計算新行的位置而做出額外的消耗。**③**減少了頁分裂和碎片的產生。
**使用自增 id 的缺點**
**①**別人一旦爬取你的數據庫,就可以根據數據庫的自增 id 獲取到你的業務增長信息,很容易分析出你的經營情況。
**②**對于高并發的負載,InnoDB 在按主鍵進行插入的時候會造成明顯的鎖爭用,主鍵的上界會成為爭搶的熱點,因為所有的插入都發生在這里,并發插入會導致間隙鎖競爭。
**③**Auto\_Increment 鎖機制會造成自增鎖的搶奪,有一定的性能損失。
附:Auto\_increment的鎖爭搶問題,如果要改善需要調優 innodb\_autoinc\_lock\_mode 的配置。
因為 uuid 相對順序的自增 id 來說是毫無規律可言的,新行的值不一定要比之前的主鍵的值要大,所以 innodb 無法做到總是把新行插入到索引的最后,而是需要為新行尋找新的合適的位置從而來分配新的空間。
這個過程需要做很多額外的操作,數據的毫無順序會導致數據分布散亂,將會導致以下的問題:
** ①**寫入的目標頁很可能已經刷新到磁盤上并且從緩存上移除,或者還沒有被加載到緩存中,innodb 在插入之前不得不先找到并從磁盤讀取目標頁到內存中,這將導致大量的隨機 IO。
** ②**因為寫入是亂序的,innodb 不得不頻繁的做頁分裂操作,以便為新的行分配空間,頁分裂導致移動大量的數據,一次插入最少需要修改三個頁以上。
** ③**由于頻繁的頁分裂,頁會變得稀疏并被不規則的填充,最終會導致數據會有碎片。
在把隨機值(uuid 和雪花 id)載入到聚簇索引(InnoDB 默認的索引類型)以后,有時候會需要做一次 OPTIMEIZE TABLE 來重建表并優化頁的填充,這將又需要一定的時間消耗。
* 結論:使用 InnoDB 應該盡可能的按主鍵的自增順序插入,并且盡可能使用單調的增加的聚簇鍵的值來插入新行。*
- 空白目錄1
- RBAC
- RBAC權限模型[完整]
- 你知道權限管理的RBAC模型嗎?
- rbac 一個用戶對應多個賬號_如何設計一個強大的權限系統
- Postman 快速使用(設置環境變量)
- postman的使用方法詳解!最全面的教程
- Postman常用的幾個功能
- ThinkPHP項目總結
- thinkphp5 遞歸查詢所有子代,查詢上級,并且獲取層級
- PHP原生項目之留言板
- 智慧校園
- PHP如何實現訂單的延時處理詳解
- VUE
- const {data:res} = await login(this.loginForm)
- Vue中的async和await的使用
- PHP實現消息推送(定時輪詢)
- tp5 計算兩個日期之間相差的天數
- 使用jquery的ajax方法獲取下拉列表值
- jQuery實現select下拉框選中數據觸發事件
- SetFocus 方法
- 快來了解下TP6中的超級函數app()!
- PHP socket 服務器框架 workerman
- 程序員如何才能成為獨立開發者?
- PHP 錯誤處理
- php面向對象類中的$this,static,final,const,self及雙冒號 :: 這幾個關鍵字使用方法。
- 小白教你玩轉php的閉包
- 關于TP6項目搭建的坑(多應用模式)
- ThinkPHP6.0 與5.0的差別及坑點
- axios在vue項目中的使用實例詳解
- php中的類、對象、方法是指什么
- 聊一聊PHP的依賴注入(DI) 和 控制反轉(IoC)
- 深入理解控制反轉(IoC)和依賴注入(DI)
- Private,Public,Protected
- ThinkPHP5(目錄,路徑,模式設置,命名空間)
- 在 ThinkPHP6 中使用 Workerman
- 介紹thinkphp lock鎖的使用和例子
- php中_initialize()函數與 __construct()函數的區別說明
- api接口數據-驗證-整理
- api接口數據-驗證-整理【續】
- TP6容易踩得坑【原創】
- TP6的日志怎么能記錄詳細的日志?
- 是否需要模型分層
- PHP面試題 全網最硬核面試題來了 2021年學習面試跳槽必備(一)
- MySQL單表數據量過千萬,采坑優化記錄,完美解決方案
- MySql表分區(根據時間timestamp)
- MySQL大表優化方案
- 閑言碎語
- 數據庫外鍵的使用
- 深入理解thinkphp、laravel等框架中容器概念
- vue做前端,thinkphp6做后臺,項目部署
- 簡單MVC架構的PHP留言本
- TP5里面extend和vendor的區別
- 在mysql數據庫中制作千萬級測試表
- MySQL千萬級的大表要怎么優化
- ThinkPHP關聯模型操作實例分析
- lcobucci/jwt —— 一個輕松生成jwt token的插件
- RESTful API 設計指南
- MySQL如何為表字段添加索引
- ThinkPHP6.0快速開發手冊(案例版)
- tp5 靜態方法和普通方法的區別
- 數據字典功能
- mysql中的數據庫ID主鍵的設置問題
- 基于角色的權限控制(django內置auth體系)
- RBAC系統經典五張表
- 什么是接口文檔,如何寫接口,有什么規范?
- thinkphp5.0自定義驗證器