自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用uuid作為數(shù)據(jù)庫(kù)主鍵,被技術(shù)總監(jiān)懟了一頓!

運(yùn)維 數(shù)據(jù)庫(kù)運(yùn)維
本篇文章主要從實(shí)際程序?qū)嵗霭l(fā),討論了三種主鍵ID生成方案的性能差異, 鑒于筆者才疏學(xué)淺,可能也有理解不到位的地方,歡迎網(wǎng)友們批評(píng)指出!

[[392375]]

本文轉(zhuǎn)載自微信公眾號(hào)「Java極客技術(shù)」,作者鴨血粉絲。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java極客技術(shù)公眾號(hào)。  

 一、摘要

在日常開(kāi)發(fā)中,數(shù)據(jù)庫(kù)中主鍵id的生成方案,主要有三種

數(shù)據(jù)庫(kù)自增ID

采用隨機(jī)數(shù)生成不重復(fù)的ID

采用jdk提供的uuid

對(duì)于這三種方案,我發(fā)現(xiàn)在數(shù)據(jù)量少的情況下,沒(méi)有特別的差異,但是當(dāng)單表的數(shù)據(jù)量達(dá)到百萬(wàn)級(jí)以上時(shí)候,他們的性能有著顯著的區(qū)別,光說(shuō)理論不行,還得看實(shí)際程序測(cè)試,今天小編就帶著大家一探究竟!

二、程序?qū)嵗?/h3>

首先,我們?cè)诒镜財(cái)?shù)據(jù)庫(kù)中創(chuàng)建三張單表tb_uuid_1、tb_uuid_2、tb_uuid_3,同時(shí)設(shè)置tb_uuid_1表的主鍵為自增長(zhǎng)模式,腳本如下:

  1. CREATE TABLE `tb_uuid_1` ( 
  2.   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
  3.   `namevarchar(20) DEFAULT NULL
  4.   PRIMARY KEY (`id`) 
  5. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主鍵ID自增長(zhǎng)'
  1. CREATE TABLE `tb_uuid_2` ( 
  2.   `id` bigint(20) unsigned NOT NULL
  3.   `namevarchar(20) DEFAULT NULL
  4.   PRIMARY KEY (`id`) 
  5. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主鍵ID隨機(jī)數(shù)生成'
  1. CREATE TABLE `tb_uuid_3` ( 
  2.   `id` varchar(50)  NOT NULL
  3.   `namevarchar(20) DEFAULT NULL
  4.   PRIMARY KEY (`id`) 
  5. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主鍵采用uuid生成'

下面,我們采用Springboot + mybatis來(lái)實(shí)現(xiàn)插入測(cè)試。

2.1、數(shù)據(jù)庫(kù)自增

以數(shù)據(jù)庫(kù)自增為例,首先編寫(xiě)好各種實(shí)體、數(shù)據(jù)持久層操作,方便后續(xù)進(jìn)行測(cè)試

  1. /** 
  2.  * 表實(shí)體 
  3.  */ 
  4. public class UUID1 implements Serializable { 
  5.  
  6.     private Long id; 
  7.  
  8.     private String name
  9.    
  10.   //省略set、get 
  1. /** 
  2.  * 數(shù)據(jù)持久層操作 
  3.  */ 
  4. public interface UUID1Mapper { 
  5.  
  6.     /** 
  7.      * 自增長(zhǎng)插入 
  8.      * @param uuid1 
  9.      */ 
  10.     @Insert("INSERT INTO tb_uuid_1(name) VALUES(#{name})"
  11.     void insert(UUID1 uuid1); 
  1. /** 
  2.  * 自增ID,單元測(cè)試 
  3.  */ 
  4. @Test 
  5. public void testInsert1(){ 
  6.     long start = System.currentTimeMillis(); 
  7.     for (int i = 0; i < 1000000; i++) { 
  8.         uuid1Mapper.insert(new UUID1().setName("張三")); 
  9.     } 
  10.     long end = System.currentTimeMillis(); 
  11.     System.out.println("花費(fèi)時(shí)間:" +  (end - start)); 

2.2、采用隨機(jī)數(shù)生成ID

這里,我們采用twitter的雪花算法來(lái)實(shí)現(xiàn)隨機(jī)數(shù)ID的生成,工具類(lèi)如下:

  1. public class SnowflakeIdWorker { 
  2.  
  3.     private static SnowflakeIdWorker instance = new SnowflakeIdWorker(0,0); 
  4.  
  5.     /** 
  6.      * 開(kāi)始時(shí)間截 (2015-01-01) 
  7.      */ 
  8.     private final long twepoch = 1420041600000L; 
  9.     /** 
  10.      * 機(jī)器id所占的位數(shù) 
  11.      */ 
  12.     private final long workerIdBits = 5L; 
  13.     /** 
  14.      * 數(shù)據(jù)標(biāo)識(shí)id所占的位數(shù) 
  15.      */ 
  16.     private final long datacenterIdBits = 5L; 
  17.     /** 
  18.      * 支持的最大機(jī)器id,結(jié)果是31 (這個(gè)移位算法可以很快的計(jì)算出幾位二進(jìn)制數(shù)所能表示的最大十進(jìn)制數(shù)) 
  19.      */ 
  20.     private final long maxWorkerId = -1L ^ (-1L << workerIdBits); 
  21.     /** 
  22.      * 支持的最大數(shù)據(jù)標(biāo)識(shí)id,結(jié)果是31 
  23.      */ 
  24.     private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 
  25.     /** 
  26.      * 序列在id中占的位數(shù) 
  27.      */ 
  28.     private final long sequenceBits = 12L; 
  29.     /** 
  30.      * 機(jī)器ID向左移12位 
  31.      */ 
  32.     private final long workerIdShift = sequenceBits; 
  33.     /** 
  34.      * 數(shù)據(jù)標(biāo)識(shí)id向左移17位(12+5) 
  35.      */ 
  36.     private final long datacenterIdShift = sequenceBits + workerIdBits; 
  37.     /** 
  38.      * 時(shí)間截向左移22位(5+5+12) 
  39.      */ 
  40.     private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; 
  41.     /** 
  42.      * 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) 
  43.      */ 
  44.     private final long sequenceMask = -1L ^ (-1L << sequenceBits); 
  45.     /** 
  46.      * 工作機(jī)器ID(0~31) 
  47.      */ 
  48.     private long workerId; 
  49.     /** 
  50.      * 數(shù)據(jù)中心ID(0~31) 
  51.      */ 
  52.     private long datacenterId; 
  53.     /** 
  54.      * 毫秒內(nèi)序列(0~4095) 
  55.      */ 
  56.     private long sequence = 0L; 
  57.     /** 
  58.      * 上次生成ID的時(shí)間截 
  59.      */ 
  60.     private long lastTimestamp = -1L; 
  61.     /** 
  62.      * 構(gòu)造函數(shù) 
  63.      * @param workerId     工作ID (0~31) 
  64.      * @param datacenterId 數(shù)據(jù)中心ID (0~31) 
  65.      */ 
  66.     public SnowflakeIdWorker(long workerId, long datacenterId) { 
  67.         if (workerId > maxWorkerId || workerId < 0) { 
  68.             throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); 
  69.         } 
  70.         if (datacenterId > maxDatacenterId || datacenterId < 0) { 
  71.             throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); 
  72.         } 
  73.         this.workerId = workerId; 
  74.         this.datacenterId = datacenterId; 
  75.     } 
  76.     /** 
  77.      * 獲得下一個(gè)ID (該方法是線程安全的) 
  78.      * @return SnowflakeId 
  79.      */ 
  80.     public synchronized long nextId() { 
  81.         long timestamp = timeGen(); 
  82.         // 如果當(dāng)前時(shí)間小于上一次ID生成的時(shí)間戳,說(shuō)明系統(tǒng)時(shí)鐘回退過(guò)這個(gè)時(shí)候應(yīng)當(dāng)拋出異常 
  83.         if (timestamp < lastTimestamp) { 
  84.             throw new RuntimeException( 
  85.                     String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); 
  86.         } 
  87.         // 如果是同一時(shí)間生成的,則進(jìn)行毫秒內(nèi)序列 
  88.         if (lastTimestamp == timestamp) { 
  89.             sequence = (sequence + 1) & sequenceMask; 
  90.             // 毫秒內(nèi)序列溢出 
  91.             if (sequence == 0) { 
  92.                 //阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳 
  93.                 timestamp = tilNextMillis(lastTimestamp); 
  94.             } 
  95.         } 
  96.         // 時(shí)間戳改變,毫秒內(nèi)序列重置 
  97.         else { 
  98.             sequence = 0L; 
  99.         } 
  100.         // 上次生成ID的時(shí)間截 
  101.         lastTimestamp = timestamp
  102.         // 移位并通過(guò)或運(yùn)算拼到一起組成64位的ID 
  103.         return ((timestamp - twepoch) << timestampLeftShift) // 
  104.                 | (datacenterId << datacenterIdShift) // 
  105.                 | (workerId << workerIdShift) // 
  106.                 | sequence
  107.     } 
  108.     /** 
  109.      * 阻塞到下一個(gè)毫秒,直到獲得新的時(shí)間戳 
  110.      * @param lastTimestamp 上次生成ID的時(shí)間截 
  111.      * @return 當(dāng)前時(shí)間戳 
  112.      */ 
  113.     protected long tilNextMillis(long lastTimestamp) { 
  114.         long timestamp = timeGen(); 
  115.         while (timestamp <= lastTimestamp) { 
  116.             timestamp = timeGen(); 
  117.         } 
  118.         return timestamp
  119.     } 
  120.     /** 
  121.      * 返回以毫秒為單位的當(dāng)前時(shí)間 
  122.      * @return 當(dāng)前時(shí)間(毫秒) 
  123.      */ 
  124.     protected long timeGen() { 
  125.         return System.currentTimeMillis(); 
  126.     } 
  127.  
  128.     public static SnowflakeIdWorker getInstance(){ 
  129.         return instance; 
  130.     } 
  131.  
  132.  
  133.     public static void main(String[] args) throws InterruptedException { 
  134.         SnowflakeIdWorker idWorker = SnowflakeIdWorker.getInstance(); 
  135.         for (int i = 0; i < 10; i++) { 
  136.             long id = idWorker.nextId(); 
  137.             Thread.sleep(1); 
  138.             System.out.println(id); 
  139.         } 
  140.     } 

其他的操作,與上面類(lèi)似。

2.3、uuid

同樣的,uuid的生成,我們事先也可以將工具類(lèi)編寫(xiě)好:

  1. public class UUIDGenerator { 
  2.  
  3.     /** 
  4.      * 獲取uuid 
  5.      * @return 
  6.      */ 
  7.     public static String getUUID(){ 
  8.         return UUID.randomUUID().toString(); 
  9.     } 

最后的單元測(cè)試,代碼如下:

  1. @RunWith(SpringRunner.class) 
  2. @SpringBootTest() 
  3. public class UUID1Test { 
  4.  
  5.     private static final Integer MAX_COUNT = 1000000; 
  6.  
  7.     @Autowired 
  8.     private UUID1Mapper uuid1Mapper; 
  9.  
  10.     @Autowired 
  11.     private UUID2Mapper uuid2Mapper; 
  12.  
  13.     @Autowired 
  14.     private UUID3Mapper uuid3Mapper; 
  15.  
  16.     /** 
  17.      * 測(cè)試自增ID耗時(shí) 
  18.      */ 
  19.     @Test 
  20.     public void testInsert1(){ 
  21.         long start = System.currentTimeMillis(); 
  22.         for (int i = 0; i < MAX_COUNT; i++) { 
  23.             uuid1Mapper.insert(new UUID1().setName("張三")); 
  24.         } 
  25.         long end = System.currentTimeMillis(); 
  26.         System.out.println("自增ID,花費(fèi)時(shí)間:" +  (end - start)); 
  27.     } 
  28.  
  29.     /** 
  30.      * 測(cè)試采用雪花算法生產(chǎn)的隨機(jī)數(shù)ID耗時(shí) 
  31.      */ 
  32.     @Test 
  33.     public void testInsert2(){ 
  34.         long start = System.currentTimeMillis(); 
  35.         for (int i = 0; i < MAX_COUNT; i++) { 
  36.             long id = SnowflakeIdWorker.getInstance().nextId(); 
  37.             uuid2Mapper.insert(new UUID2().setId(id).setName("張三")); 
  38.         } 
  39.         long end = System.currentTimeMillis(); 
  40.         System.out.println("花費(fèi)時(shí)間:" +  (end - start)); 
  41.     } 
  42.  
  43.     /** 
  44.      * 測(cè)試采用UUID生成的ID耗時(shí) 
  45.      */ 
  46.     @Test 
  47.     public void testInsert3(){ 
  48.         long start = System.currentTimeMillis(); 
  49.         for (int i = 0; i < MAX_COUNT; i++) { 
  50.             String id = UUIDGenerator.getUUID(); 
  51.             uuid3Mapper.insert(new UUID3().setId(id).setName("張三")); 
  52.         } 
  53.         long end = System.currentTimeMillis(); 
  54.         System.out.println("花費(fèi)時(shí)間:" +  (end - start)); 
  55.     } 

三、性能測(cè)試

程序環(huán)境搭建完成之后,啥也不說(shuō)了,直接擼起袖子,將單元測(cè)試跑起來(lái)!

首先測(cè)試一下,插入100萬(wàn)數(shù)據(jù)的情況下,三者直接的耗時(shí)結(jié)果如下:

在原有的數(shù)據(jù)量上,我們繼續(xù)插入30萬(wàn)條數(shù)據(jù),三者耗時(shí)結(jié)果如下:

可以看出在數(shù)據(jù)量 100W 左右的時(shí)候,uuid的插入效率墊底,隨著插入的數(shù)據(jù)量增長(zhǎng),uuid 生成的ID插入呈直線下降!

時(shí)間占用量總體效率排名為:自增ID > 雪花算法生成的ID >> uuid生成的ID。

在數(shù)據(jù)量較大的情況下,為什么uuid生成的ID遠(yuǎn)不如自增ID呢?

關(guān)于這點(diǎn),我們可以從 mysql 主鍵存儲(chǔ)的內(nèi)部結(jié)構(gòu)來(lái)進(jìn)行分析。

3.1、自增ID內(nèi)部結(jié)構(gòu)

自增的主鍵的值是順序的,所以 Innodb 把每一條記錄都存儲(chǔ)在一條記錄的后面。

當(dāng)達(dá)到頁(yè)面的最大填充因子時(shí)候(innodb默認(rèn)的最大填充因子是頁(yè)大小的15/16,會(huì)留出1/16的空間留作以后的修改),會(huì)進(jìn)行如下操作:

  • 下一條記錄就會(huì)寫(xiě)入新的頁(yè)中,一旦數(shù)據(jù)按照這種順序的方式加載,主鍵頁(yè)就會(huì)近乎于順序的記錄填滿,提升了頁(yè)面的最大填充率,不會(huì)有頁(yè)的浪費(fèi)
  • 新插入的行一定會(huì)在原有的最大數(shù)據(jù)行下一行,mysql定位和尋址很快,不會(huì)為計(jì)算新行的位置而做出額外的消耗

3.2、使用uuid的索引內(nèi)部結(jié)構(gòu)

uuid相對(duì)順序的自增id來(lái)說(shuō)是毫無(wú)規(guī)律可言的,新行的值不一定要比之前的主鍵的值要大,所以innodb無(wú)法做到總是把新行插入到索引的最后,而是需要為新行尋找新的合適的位置從而來(lái)分配新的空間。

這個(gè)過(guò)程需要做很多額外的操作,數(shù)據(jù)的毫無(wú)順序會(huì)導(dǎo)致數(shù)據(jù)分布散亂,將會(huì)導(dǎo)致以下的問(wèn)題:

  • 寫(xiě)入的目標(biāo)頁(yè)很可能已經(jīng)刷新到磁盤(pán)上并且從緩存上移除,或者還沒(méi)有被加載到緩存中,innodb在插入之前不得不先找到并從磁盤(pán)讀取目標(biāo)頁(yè)到內(nèi)存中,這將導(dǎo)致大量的隨機(jī)IO
  • 因?yàn)閷?xiě)入是亂序的,innodb不得不頻繁的做頁(yè)分裂操作,以便為新的行分配空間,頁(yè)分裂導(dǎo)致移動(dòng)大量的數(shù)據(jù),一次插入最少需要修改三個(gè)頁(yè)以上
  • 由于頻繁的頁(yè)分裂,頁(yè)會(huì)變得稀疏并被不規(guī)則的填充,最終會(huì)導(dǎo)致數(shù)據(jù)會(huì)有碎片

在把值載入到聚簇索引(innodb默認(rèn)的索引類(lèi)型)以后,有時(shí)候會(huì)需要做一次OPTIMEIZE TABLE來(lái)重建表并優(yōu)化頁(yè)的填充,這將又需要一定的時(shí)間消耗。

因此,在選擇主鍵ID生成方案的時(shí)候,盡可能別采用uuid的方式來(lái)生成主鍵ID,隨著數(shù)據(jù)量越大,插入性能會(huì)越低!

四、總結(jié)

在實(shí)際使用過(guò)程中,推薦使用主鍵自增ID和雪花算法生成的隨機(jī)ID。

但是使用自增ID也有缺點(diǎn):

1、別人一旦爬取你的數(shù)據(jù)庫(kù),就可以根據(jù)數(shù)據(jù)庫(kù)的自增id獲取到你的業(yè)務(wù)增長(zhǎng)信息,很容易進(jìn)行數(shù)據(jù)竊取。2、其次,對(duì)于高并發(fā)的負(fù)載,innodb在按主鍵進(jìn)行插入的時(shí)候會(huì)造成明顯的鎖爭(zhēng)用,主鍵的上界會(huì)成為爭(zhēng)搶的熱點(diǎn),因?yàn)樗械牟迦攵及l(fā)生在這里,并發(fā)插入會(huì)導(dǎo)致間隙鎖競(jìng)爭(zhēng)。

總結(jié)起來(lái),如果業(yè)務(wù)量小,推薦采用自增ID,如果業(yè)務(wù)量大,推薦采用雪花算法生成的隨機(jī)ID。

本篇文章主要從實(shí)際程序?qū)嵗霭l(fā),討論了三種主鍵ID生成方案的性能差異, 鑒于筆者才疏學(xué)淺,可能也有理解不到位的地方,歡迎網(wǎng)友們批評(píng)指出!

 

責(zé)任編輯:武曉燕 來(lái)源: Java極客技術(shù)
相關(guān)推薦

2021-05-12 07:32:58

數(shù)據(jù)庫(kù)C3P0項(xiàng)目

2023-08-28 12:07:06

UUIDMySQL

2024-08-12 00:00:00

NPMCTOJavaScrip

2021-02-09 16:31:30

物聯(lián)網(wǎng)年夜飯餐飲

2024-06-05 10:21:30

2021-12-17 07:30:42

排序算法效率

2020-08-31 11:20:53

MySQLuuidid

2019-08-20 08:36:15

混淆堆棧Android

2019-11-21 13:59:20

網(wǎng)絡(luò)攻擊攻擊成本網(wǎng)絡(luò)安全

2022-03-22 09:00:00

數(shù)據(jù)庫(kù)SingleStor技術(shù)

2023-10-10 11:18:42

Spring數(shù)據(jù)庫(kù)

2021-05-18 09:39:19

互聯(lián)網(wǎng)操作系統(tǒng)Go

2018-04-12 17:00:07

云計(jì)算燒烤SaaS

2015-05-07 10:02:47

庫(kù)克蘋(píng)果

2024-07-17 09:16:58

2022-03-30 09:30:00

數(shù)據(jù)庫(kù)地理空間查詢SQL

2011-07-25 18:11:47

SQL Server數(shù)復(fù)合主鍵

2024-10-24 09:22:30

2019-07-26 15:45:50

技術(shù)經(jīng)理項(xiàng)目管理

2020-02-24 08:00:30

刪庫(kù)跑路判刑
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)