天天看點

一起看看MySQL中的隐藏列

摘要:在mysql的多版本并發控制mvcc中,我們知道mysql中存在一些隐藏列,例如行辨別、事務ID、復原指針等,不知道大家是否和我一樣好奇過,要怎樣才能實際地看到這些隐藏列的值呢?

本文分享自華為雲社群​​《眼見為實,看看MySQL中的隐藏列!》​​,作者:碼農參上。

在mysql的多版本并發控制mvcc中,我們知道mysql中存在一些隐藏列,例如行辨別、事務ID、復原指針等,不知道大家是否和我一樣好奇過,要怎樣才能實際地看到這些隐藏列的值呢?

本文我們就來重點讨論一下諸多隐藏列中的行辨別DB_ROW_ID,實際上,将行辨別稱為隐藏列并不準确,因為它并不是一個真實存在的列,DB_ROW_ID實際上是一個非空唯一列的别名。在撥開它的神秘面紗之前,我們看一下官方文檔的說明:

If a table has a PRIMARY KEY or UNIQUE NOT NULL index that consists of a single column that has an integer type, you can use _rowid to refer to the indexed column in SELECT statements

簡單翻譯一下,如果在表中存在主鍵或非空唯一索引,并且僅由一個整數類型的列構成,那麼就可以使用SELECT語句直接查詢_rowid,并且這個_rowid的值會引用該索引列的值。

着重看一下文檔中提到的幾個關鍵字,主鍵、唯一索引、非空、單獨一列、數值類型,接下來我們就要從這些角度入手,探究一下神秘的隐藏字段_rowid。

1、存在主鍵

先看設定了主鍵且是數值類型的情況,使用下面的語句建表:

CREATE TABLE `table1` (
  `id` bigint(20) NOT NULL PRIMARY KEY ,
  `name` varchar(32) DEFAULT NULL
) ENGINE=InnoDB;      

插入三條測試資料後,執行下面的查詢語句,在select查詢語句中直接查詢_rowid:

select *,_rowid from table1      

檢視執行結果,_rowid可以被正常查詢:

一起看看MySQL中的隐藏列

可以看到在設定了主鍵,并且主鍵字段是數值類型的情況下,_rowid直接引用了主鍵字段的值。對于這種可以被select語句查詢到的的情況,可以将其稱為顯式的rowid。

回顧一下前面提到的文檔中的幾個關鍵字,分别對其進行分析。由于主鍵必定是非空字段,下面來看一下主鍵是非數值類型字段的情況,建表如下:

CREATE TABLE `table2` (
  `id` varchar(20) NOT NULL PRIMARY KEY ,
  `name` varchar(32) DEFAULT NULL
) ENGINE=InnoDB;      

在table2執行上面相同的查詢,結果報錯無法查詢_rowid,也就證明了如果主鍵字段是非數值類型,那麼将無法直接查詢_rowid。

一起看看MySQL中的隐藏列

 2、無主鍵,存在唯一索引

上面對兩種類型的主鍵進行了測試後,接下來我們看一下當表中沒有主鍵、但存在唯一索引的情況。首先測試非空唯一索引加在數值類型字段的情況,建表如下:

CREATE TABLE `table3` (
  `id` bigint(20) NOT NULL UNIQUE KEY,
  `name` varchar(32)
) ENGINE=InnoDB;      

查詢可以正常執行,并且_rowid引用了唯一索引所在列的值:

一起看看MySQL中的隐藏列

唯一索引與主鍵不同的是,唯一索引所在的字段可以為NULL。在上面的table3中,在唯一索引所在的列上添加了NOT NULL非空限制,如果我們把這個非空限制删除掉,還能顯式地查詢到_rowid嗎?下面再建立一個表,不同是在唯一索引所在的列上,不添加非空限制:

CREATE TABLE `table4` (
  `id` bigint(20) UNIQUE KEY,
  `name` varchar(32)
) ENGINE=InnoDB;      

執行查詢語句,在這種情況下,無法顯式地查詢到_rowid:

一起看看MySQL中的隐藏列

和主鍵類似的,我們再對唯一索引被加在非數值類型的字段的情況進行測試。下面在建表時将唯一索引添加在字元類型的字段上,并添加非空限制:

CREATE TABLE `table5` (
  `id` bigint(20),
  `name` varchar(32) NOT NULL UNIQUE KEY
) ENGINE=InnoDB;      

同樣無法顯示的查詢到_rowid:

一起看看MySQL中的隐藏列

針對上面三種情況的測試結果,可以得出結論,當沒有主鍵、但存在唯一索引的情況下,隻有該唯一索引被添加在數值類型的字段上,且該字段添加了非空限制時,才能夠顯式地查詢到_rowid,并且_rowid引用了這個唯一索引字段的值。

3、存在聯合主鍵或聯合唯一索引

在上面的測試中,我們都是将主鍵或唯一索引作用在單獨的一列上,那麼如果使用了聯合主鍵或聯合唯一索引時,結果會如何呢?還是先看一下官方文檔中的說明:

_rowid refers to the PRIMARY KEY column if there is a PRIMARY KEY consisting of a single integer column. If there is a PRIMARY KEY but it does not consist of a single integer column, _rowid cannot be used.

簡單來說就是,如果主鍵存在、且僅由數值類型的一列構成,那麼_rowid的值會引用主鍵。如果主鍵是由多列構成,那麼_rowid将不可用。

根據這一描述,我們測試一下聯合主鍵的情況,下面将兩列數值類型字段作為聯合主鍵建表:

CREATE TABLE `table6` (
  `id` bigint(20) NOT NULL,
  `no` bigint(20) NOT NULL,
  `name` varchar(32),
  PRIMARY KEY(`id`,`no`)
) ENGINE=InnoDB;      

執行結果無法顯示的查詢到_rowid:

一起看看MySQL中的隐藏列

同樣,這一理論也可以作用于唯一索引,如果非空唯一索引不是由單獨一列構成,那麼也無法直接查詢得到_rowid。這一測試過程省略,有興趣的小夥伴可以自己動手試試。

4、存在多個唯一索引

在mysql中,每張表隻能存在一個主鍵,但是可以存在多個唯一索引。那麼如果同時存在多個符合規則的唯一索引,會引用哪個作為_rowid的值呢?老規矩,還是看官方文檔的解答:

Otherwise, _rowid refers to the column in the first UNIQUE NOT NULL index if that index consists of a single integer column. If the first UNIQUE NOT NULL index does not consist of a single integer column, _rowid cannot be used.

簡單翻譯一下,如果表中的第一個非空唯一索引僅由一個整數類型字段構成,那麼_rowid會引用這個字段的值。否則,如果第一個非空唯一索引不滿足這種情況,那麼_rowid将不可用。

在下面的表中,建立兩個都符合規則的唯一索引:

CREATE TABLE `table8_2` (
  `id` bigint(20) NOT NULL,
  `no` bigint(20) NOT NULL,
  `name` varchar(32),
  UNIQUE KEY(no),
  UNIQUE KEY(id)
) ENGINE=InnoDB;      

看一下執行查詢語句的結果:

一起看看MySQL中的隐藏列

可以看到_rowid的值與no這一列的值相同,證明了_rowid會嚴格地選取第一個建立的唯一索引作為它的引用。

那麼,如果表中建立的第一個唯一索引不符合_rowid的引用規則,第二個唯一索引滿足規則,這種情況下,_rowid可以被顯示地查詢嗎?針對這種情況我們建表如下,表中的第一個索引是聯合唯一索引,第二個索引才是單列的唯一索引情況,再來進行一下測試:

CREATE TABLE `table9` (
  `id` bigint(20) NOT NULL,
  `no` bigint(20) NOT NULL,
  `name` varchar(32),
  UNIQUE KEY `index1`(`id`,`no`),
  UNIQUE KEY `index2`(`id`)
) ENGINE=InnoDB;      

進行查詢,可以看到雖然存在一個單列的非空唯一索引,但是因為順序選取的第一個不滿足要求,是以仍然不能直接查詢_rowid:

一起看看MySQL中的隐藏列

如果将上面建立唯一索引的語句順序調換,那麼将可以正常顯式的查詢到_rowid。

5、同時存在主鍵與唯一索引

從上面的例子中,可以看到唯一索引的定義順序會決定将哪一個索引應用_rowid,那麼當同時存在主鍵和唯一索引時,定義順序會對其引用造成影響嗎?

按照下面的語句建立兩個表,隻有建立主鍵和唯一索引的順序不同:

CREATE TABLE `table11` (
  `id` bigint(20) NOT NULL,
  `no` bigint(20) NOT NULL,
  PRIMARY KEY(id),
  UNIQUE KEY(no)
) ENGINE=InnoDB;

CREATE TABLE `table12` (
  `id` bigint(20) NOT NULL,
  `no` bigint(20) NOT NULL,
  UNIQUE KEY(id),
  PRIMARY KEY(no)
) ENGINE=InnoDB;      

檢視運作結果:

一起看看MySQL中的隐藏列

可以得出結論,當同時存在符合條件的主鍵和唯一索引時,無論建立順序如何,_rowid都會優先引用主鍵字段的值。

6、無符合條件的主鍵與唯一索引

上面,我們把能夠直接通過select語句查詢到的稱為顯式的_rowid,在其他情況下雖然_rowid不能被顯式查詢,但是它也是一直存在的,這種情況我們可以将其稱為隐式的_rowid。

實際上,innoDB在沒有預設主鍵的情況下會生成一個6位元組長度的無符号數作為自動增長的_rowid,是以最大為2^48-1,到達最大值後會從0開始計算。下面,我們建立一個沒有主鍵與唯一索引的表,在這張表的基礎上,探究一下隐式的_rowid。

CREATE TABLE `table10` (
  `id` bigint(20),
  `name` varchar(32)
) ENGINE=InnoDB;      

首先,我們需要先查找到mysql的程序pid:

ps -ef | grep mysqld      

可以看到,mysql的程序pid是2068:

一起看看MySQL中的隐藏列

在開始動手前,還需要做一點鋪墊, 在innoDB中其實維護了一個全局變量dictsys.row_id,沒有定義主鍵的表都會共享使用這個row_id,在插入資料時會把這個全局row_id當作自己的主鍵,然後再将這個全局變量加 1。

接下來我們需要用到gdb調試的相關技術,gdb是一個在Linux下的調試工具,可以用來調試可執行檔案。在伺服器上,先通過yum install gdb安裝,安裝完成後,通過下面的gdb指令 把 row_id 修改為 1:

gdb -p 2068 -ex 'p dict_sys->row_id=1' -batch      

指令執行結果:

一起看看MySQL中的隐藏列

在空表中插入3行資料:

INSERT INTO table10 VALUES (100000001, 'Hydra');
INSERT INTO table10 VALUES (100000002, 'Trunks');
INSERT INTO table10 VALUES (100000003, 'Susan');      

檢視表中的資料,此時對應的_rowid理論上是1~3:

一起看看MySQL中的隐藏列

然後通過gdb指令把row_id改為最大值2^48,此時已超過dictsys.row_id最大值:

gdb -p 2068 -ex 'p dict_sys->row_id=281474976710656' -batch      

指令執行結果:

一起看看MySQL中的隐藏列

再向表中插入三條資料:

INSERT INTO table10 VALUES (100000004, 'King');
INSERT INTO table10 VALUES (100000005, 'Queen');
INSERT INTO table10 VALUES (100000006, 'Jack');      

檢視表中的全部資料,可以看到第一次插入的三條資料中,有兩條資料被覆寫了:

一起看看MySQL中的隐藏列

為什麼會出現資料覆寫的情況呢,我們對這一結果進行分析。首先,在第一次插入資料前_rowid為1,插入的三條資料對應的_rowid為1、2、3。如下圖所示:

一起看看MySQL中的隐藏列

當手動設定_rowid為最大值後,下一次插入資料時,插入的_rowid重新從0開始,是以第二次插入的三條資料的_rowid應該為0、1、2。這時準備被插入的資料如下所示:

一起看看MySQL中的隐藏列

當出現相同_rowid的情況下,新插入的資料會根據_rowid覆寫掉原有的資料,過程如圖所示:

一起看看MySQL中的隐藏列

是以當表中的主鍵或唯一索引不滿足我們前面提到的要求時,innoDB使用的隐式的_rowid是存在一定風險的,雖然說2^48這個值很大,但還是有可能被用盡的,當_rowid用盡後,之前的記錄就會被覆寫。從這一角度也可以提醒大家,在建表時一定要建立主鍵,否則就有可能發生資料的覆寫。