假設我們有一個用戶表,每個用戶都有一個UUID。MySQL有一個UUID()函數,它使MySQL生成一個UUID值,並以VARCHAR(36)類型的可讀形式返回。讓我們試試MySQL 5.7.8:
mysql> select uuid();
+--------------------------------------+
| uuid() |
+--------------------------------------+
| aab5d5fd-70c1-11e5-a4fb-b026b977eb28 |
+--------------------------------------+
mysql> select uuid();
+--------------------------------------+
| uuid() |
+--------------------------------------+
| aab5d5fd-70c1-11e5-a4fb-b026b977eb28 |
+--------------------------------------+
所以第一個想法是簡單地做到這一點:
create table users(id varchar(36), name varchar(200));
insert into users values(uuid(), 'Andromeda');
create table users(id varchar(36), name varchar(200));
insert into users values(uuid(), 'Andromeda');
但是這種UUID的可讀形式並不緊湊。讓我們觀察一下:
四個破折號是多余的
每對字符實際上是一個在00-FF范圍內的十六進制數; 總共有16個數字(以上為:0xAA,0xB5等),每個數字可以存儲在一個字節中。
所以我們可以使用REPLACE()去掉破折號,UNHEX()把每個
雙字符對轉換成一個字節:
create table users(id_bin binary(16), name varchar(200));
insert into users values(unhex(replace(uuid(),'-','')), 'Andromeda');
create table users(id_bin binary(16), name varchar(200));
insert into users values(unhex(replace(uuid(),'-','')), 'Andromeda');
這個二進制形式使用16個字節,比人類可讀形式(我現在稱之為“文本”形式)使用的VARCHAR(36)小得多。如果UUID必須是主鍵,則增益更大,如InnoDB中的主鍵值被復制到所有二級索引值中。
二進制(16)是...好吧...只是二進制!沒有字符集,沒有排序,只有十六個字節。適合我們的需求。
也許在某些應用程序中,文本形式仍然是必需的,所以讓我們把它作為表格中的一個附加列; 但為了盡量減少磁盤占用,讓我們將文本形成一個虛擬的生成 列(這是MySQL 5.7的一個新特性,在CREATE TABLE的文檔中有描述)。該列將通過二進制格式列的公式計算:我們將二進制格式轉換回十六進制數字並插入破折號。
create table users(
id_bin binary(16),
id_text varchar(36) generated always as
(insert(
insert(
insert(
insert(hex(id_bin),9,0,'-'),
14,0,'-'),
19,0,'-'),
24,0,'-')
) virtual,
name varchar(200));
insert into users (id_bin,name)
values(unhex(replace(uuid(),'-','')), 'Andromeda');
select id_text, name from users;
+--------------------------------------+-----------+
| id_text | name |
+--------------------------------------+-----------+
| C2770D2E-70E6-11E5-A4FB-B026B977EB28 | Andromeda |
+--------------------------------------+-----------+
create table users(
id_bin binary(16),
id_text varchar(36) generated always as
(insert(
insert(
insert(
insert(hex(id_bin),9,0,'-'),
14,0,'-'),
19,0,'-'),
24,0,'-')
) virtual,
name varchar(200));
insert into users (id_bin,name)
values(unhex(replace(uuid(),'-','')), 'Andromeda');
select id_text, name from users;
+--------------------------------------+-----------+
| id_text | name |
+--------------------------------------+-----------+
| C2770D2E-70E6-11E5-A4FB-B026B977EB28 | Andromeda |
+--------------------------------------+-----------+
我沒有在SELECT中包含id_bin,因為它會以隱藏字符(ASCII碼0xC2,0x77等:通常不在人可讀的字符范圍內)出現。我們沒有理由需要看id_bin的內容; 但是,如果這樣做,則可以使用HEX(id_bin)來顯示其十六進制代碼。
請注意,id_text被聲明為VIRTUAL,因此在磁盤上的表中不占用空間。
使id_text成為生成列的另一個好處是消除了兩列之間的任何不一致的風險。事實上,如果id_text是一個簡單的列,可以做
update users set id_bin = <something>;
而無意更新id_text。但是作為一個生成的列,id_text不能直接更新,而是在更新id_bin時自動更新。換句話說,信息只在一個地方(id_bin),數據庫保證了一致性。
那么,那么查詢呢?例如,我們可能想通過UUID找到一個用戶:
select * from users where <it has UUID XYZ>;
WHERE子句應該指定二進制還是文本形式?這取決於:
如果我們創建一個二進制形式的索引:
alter table users add unique(id_bin);
那么,為了使用這個索引,WHERE應該指定二進制形式:
WHERE id_bin = binary_form_of_XYZ
相反,如果我們在文本表單上創建一個索引:
alter table users add unique(id_text);
那么,WHERE應該指定文本形式:
WHERE id_text = text_form_of_XYZ
即使id_text是一個虛擬列,也可以像上面那樣在其上添加索引(在這種情況下,索引占用磁盤空間)。這是MySQL 5.7.8中引入的一個新功能。
但是,如果我們有一個選擇,由於二進制形式更短,它索引它看起來更合乎邏輯,而不是文本形式 - 索引將更小,從而更快地遍歷,更快的備份...
最后,還有如何巧妙地重新排列二進制形式的字節的問題。
要了解這一點,我們需要了解更多關於UUID的信息。它們存在幾個版本,不同的來源可以生成不同的版本。MySQL的UUID()使用版本1,這意味着,正如在RFC 4.1.2中所解釋的那樣,三個最左邊的以破折號分隔的組是8字節的時間戳:最左邊的組是時間戳的低四個字節; 第二組是中間兩個字節,第三個組是高(最重要)兩個字節的時間戳。因此,最左邊的組變化最快(每微秒10次)。我們可以驗證:
mysql> select uuid(); do sleep(2); select uuid();
+--------------------------------------+
| uuid() |
+--------------------------------------+
| 3b96402f-70c5-11e5-a4fb-b026b977eb28 |
+--------------------------------------+
+--------------------------------------+
| uuid() |
+--------------------------------------+
| 3cc7f7dc-70c5-11e5-a4fb-b026b977eb28 |
+--------------------------------------+
mysql> select uuid(); do sleep(2); select uuid();
+--------------------------------------+
| uuid() |
+--------------------------------------+
| 3b96402f-70c5-11e5-a4fb-b026b977eb28 |
+--------------------------------------+
+--------------------------------------+
| uuid() |
+--------------------------------------+
| 3cc7f7dc-70c5-11e5-a4fb-b026b977eb28 |
+--------------------------------------+
你可以看到最左邊的8個字符是如何變化的,而其他的則沒有。
因此,在由單個機器連續產生的UUID序列中,所有UUID具有不同的第一字節。將該序列插入索引列(二進制或文本形式)將因此每次修改不同的索引頁面,從而防止內存中的緩存。因此,在我們存儲到id_bin之前,重新安排UUID,使得快速變化的部分走到最后,是有意義的。再一次請注意,這個想法只適用於版本1的UUID。
這個想法不是我的 ; 我在第一次看到它這個博客(http://mysql.rjweb.org/doc.php/uuid)和那一個(https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/)。
以下,通過改變時間低/時間中/時間高到時間高/時間中/時間低來重新排列二進制形式。
create table users(id_bin binary(16), name varchar(200));
set @u = unhex(replace(uuid(),'-',''));
insert into users (id_bin,name)
values
(
concat(substr(@u, 7, 2), substr(@u, 5, 2),
substr(@u, 1, 4), substr(@u, 9, 8)),
'Andromeda'
);
create table users(id_bin binary(16), name varchar(200));
set @u = unhex(replace(uuid(),'-',''));
insert into users (id_bin,name)
values
(
concat(substr(@u, 7, 2), substr(@u, 5, 2),
substr(@u, 1, 4), substr(@u, 9, 8)),
'Andromeda'
);
我在(@u)之上使用了一個用戶變量,因為每個SUBSTR()調用需要引用UUID值,但是我不能寫UUID()四次:每次都會生成一個新的UUID!所以我調用一次UUID(),刪除破折號,將其轉換為二進制文件,將其存儲在一個變量,並做它的四個SUBSTR。
但是,我仍然希望文本格式處於“未重新排列”的順序,因為...或許這個文本格式將用於一些錯誤日志記錄,調試?如果人類要閱讀它,我不想通過重新排列的順序來混淆它們。
添加id_text可以在CREATE TABLE中完成,或者作為后續的ALTER TABLE:
alter table users add
id_text varchar(36) generated always as
(
insert(
insert(
insert(
insert(
hex(
concat(substr(id_bin,5,4),substr(id_bin,3,2),
substr(id_bin,1,2),substr(id_bin,9,8))
),
9,0,'-'),
14,0,'-'),
19,0,'-'),
24,0,'-')
) virtual;
alter table users add
id_text varchar(36) generated always as
(
insert(
insert(
insert(
insert(
hex(
concat(substr(id_bin,5,4),substr(id_bin,3,2),
substr(id_bin,1,2),substr(id_bin,9,8))
),
9,0,'-'),
14,0,'-'),
19,0,'-'),
24,0,'-')
) virtual;
它將二進制形式的部分(帶有SUBSTR)放在“正常”位置(CONCAT),將字節轉換為十六進制數字(HEX)並插入破折號。對,這是一個復雜的表達式,但是在創建生成的列時只能輸入一次。
現在看看數據:
select id_bin, hex(id_bin), name, id_text from users;
+------------------+----------------------------------+-----------+--------------------------------------+
| id_bin | hex(id_bin) | name | id_text |
+------------------+----------------------------------+-----------+--------------------------------------+
| �p�:�ˤ� &�w� | 11E570EA3A059CCBA4FBB026B977EB28 | Andromeda | 3A059CCB-70EA-11E5-A4FB-B026B977EB28 |
+------------------+----------------------------------+-----------+--------------------------------------+
select id_bin, hex(id_bin), name, id_text from users;
+------------------+----------------------------------+-----------+--------------------------------------+
| id_bin | hex(id_bin) | name | id_text |
+------------------+----------------------------------+-----------+--------------------------------------+
| �p�:�ˤ� &�w� | 11E570EA3A059CCBA4FBB026B977EB28 | Andromeda | 3A059CCB-70EA-11E5-A4FB-B026B977EB28 |
+------------------+----------------------------------+-----------+--------------------------------------+
專欄是:
1.隱含字符(重新排列的二進制UUID)
2.第一列的相應的十六進制代碼
3.名字
4.未重新排列的文本UUID。看看這個文本形式最左邊的3A059CCB是如何快速變化的部分,處於二進制形式(第2列)的中間,所以二進制形式將會提供更有效的索引。
java中使用UUID.randomUUID().toString().replace("-", "").toLowerCase()來生成UUID。