MySQL 8.0.23新特性 - 不可見列

作者:lefred

譯者:孟維克
原文鏈接:

  • https://lefred.be/content/mysql-invisible-column-part-i/

  • https://lefred.be/content/mysql-invisible-column-part-ii/

  • https://lefred.be/content/mysql-invisible-column-part-iii/

在新的MySQL 8.0.23中,引入了新的有趣功能:不可見列

這是第一篇關於這個新功能的文章,我希望寫一個3篇的系列。這是前言。

在MySQL 8.0.23之前,表中所有的列都是可見的(如果您有權限的話)。現在可以指定一個不可見的列,它將對查詢隱藏。如果顯式引用,它可以被查到。

讓我們看看它是怎樣的:

create table table1 (
   id int auto_increment primary key, 
   name varchar(20), 
   age int invisible);

在表結構中我們在Extra列可以看到INVISIBLE 關鍵字:

desc table1;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int         | NO   | PRI | NULL    | auto_increment |
| name  | varchar(20) | YES  |     | NULL    |                |
| age   | int         | YES  |     | NULL    | INVISIBLE      |
+-------+-------------+------+-----+---------+----------------+

查看show create table語句,注意到有一個不同,當我創建表時,我希望看到INVISIBLE 關鍵字,但事實並非如此:

show create table table1\\G
************************* 1. row *************************
        Table: table1
 Create Table: CREATE TABLE `table1` (
   id int NOT NULL AUTO_INCREMENT,
   name varchar(20) DEFAULT NULL,
   age int DEFAULT NULL /*!80023 INVISIBLE */,
   PRIMARY KEY (id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

但是我確認這個語句在創建表時會將age 列設置爲不可見。所以我們有2個不同的語法來創建不可見列。

INFORMATION_SCHEMA 中也可以看到相關信息:

SELECT TABLE_NAME, COLUMN_NAME, EXTRA
  FROM INFORMATION_SCHEMA.COLUMNS
  WHERE TABLE_SCHEMA = 'test' AND TABLE_NAME = 'table1';
+------------+-------------+----------------+
| TABLE_NAME | COLUMN_NAME | EXTRA          |
+------------+-------------+----------------+
| table1     | id          | auto_increment |
| table1     | name        |                |
| table1     | age         | INVISIBLE      |
+------------+-------------+----------------+

插入一些數據,繼續觀察:

insert into table1 values (0,'mysql', 25), 
                          (0,'kenny', 35), 
                          (0, 'lefred','44');
 ERROR: 1136: Column count doesn't match value count at row 1

如預期,插入語句中如果我們不引用它,會報錯。引用這些列:

insert into table1 (id, name, age) 
       values (0,'mysql', 25), 
              (0,'kenny', 35), 
              (0, 'lefred','44');
 Query OK, 3 rows affected (0.1573 sec

查詢表中數據:

select * from table1;
+----+--------+
| id | name   |
+----+--------+
|  1 | mysql  |
|  2 | kenny  |
|  3 | lefred |
+----+--------+

再一次,如預期,我們看到不可見列沒有顯示。

如果我們指定它:

select name, age from table1;
+--------+-----+
| name   | age |
+--------+-----+
| mysql  |  25 |
| kenny  |  35 |
| lefred |  44 |
+--------+-----+

當然我們可以將列從可見轉爲不可見或者將不可見轉爲可見:

alter table table1 modify name varchar(20) invisible, 
                   modify age integer visible;
Query OK, 0 rows affected (0.1934 sec)

select * from table1;
+----+-----+
| id | age |
+----+-----+
|  1 |  25 |
|  2 |  35 |
|  3 |  44 |
+----+-----+

我對這個新功能感到非常高興,在下一篇文章中我們將會看到爲什麼這對InnoDB來說是一個重要的功能。

本文是與MySQL不可見列相關的系列文章的第二部分。

這篇文章介紹了爲什麼不可見列對InnoDB存儲引擎很重要。

首先,讓我簡單解釋一下InnoDB是如何處理主鍵的,以及爲什麼一個好的主鍵很重要。最後,爲什麼主鍵也很重要。

InnoDB如何存儲數據?

InnoDB在表空間存儲數據。這些記錄存儲並用聚簇索引排序(主鍵):它們被稱爲索引組織表。

所有的二級索引也將主鍵作爲索引中的最右邊的列(即使沒有公開)。這意味着當使用二級索引檢索一條記錄時,將使用兩個索引:二級索引指向用於最終檢索該記錄的主鍵。

主鍵會影響隨機I/O和順序I/O之間的比率以及二級索引的大小。

隨機主鍵還是順序主鍵?

如上所述,數據存儲在聚簇索引中的表空間中。這意味着如果您不使用順序索引,當執行插入時,InnoDB不得不重平衡表空間的所有頁。

如果我們用InnoDB Ruby來說明這個過程,下面的圖片顯示了當使用隨機字符串作爲主鍵插入記錄時表空間是如何更新的:

每次有一個插入,幾乎所有的頁都會被觸及。

當使用自增整型作爲主鍵時,同樣的插入:

自增主鍵的情況下,只有第一個頁和最後一個頁纔會被觸及。

讓我們用一個高層次的例子來解釋這一點:

假設一個InnoDB頁可以存儲4條記錄(免責聲明:這只是一個虛構的例子),我們使用隨機主鍵插入了一些記錄:

插入新記錄,主鍵爲AA!

修改所有頁以"重新平衡"聚簇索引,在連續主鍵的情況下,只有最後一個頁面會被修改。想象一下成千上萬的插入發生時所要做的額外工作。

這意味着選擇好的主鍵是重要的。需要注意兩點:

  1. 主鍵必須連續。

  2. 主鍵必須短。

UUID怎麼樣?

我通常建議使用自增整型(或bigint)作爲主鍵,但是不要忘記監控它們!

但我也明白越來越多的開發人員喜歡使用uuid。

如果您打算使用UUID,您應該閱讀MySQL8.0中UUID的支持,這篇文章推薦您用binary(16) 存儲UUID。

如:

CREATE TABLE t (id binary(16) PRIMARY KEY);
 
INSERT INTO t VALUES(UUID_TO_BIN(UUID()));

然而,我並不完全同意這個觀點,爲什麼?

因爲使用uuid_to_bin() 可能會改變MySQL的UUID實現的順序行爲(有關更多信息,請參閱額外部分)。

但是如果您需要UUID,你需要在大索引上花費一定代價,索引不要浪費存儲和內存在不需要的二級索引上:

select * from sys.schema_unused_indexes where object_schema not in ('performance_schema', 'mysql');

沒有任何主鍵?

對InnoDB表來說,當沒有定義主鍵,會使用第一個唯一非空列。如果沒有可用的列,InnoDB會創建一個隱藏主鍵(6位)。

這類主鍵的問題在於您無法控制它,更糟糕的是,這個值對所有沒有主鍵的表是全局的,如果您同時對這些表執行多次寫操作,可能會產生爭用問題(dict_sys->mutex)。

不可見列的用處

有了新的不可見列,如果應用不允許添加新列,我們現在就可以向沒有主鍵的表添加合適的主鍵。

首先先找到這些表:

SELECT tables.table_schema , tables.table_name , tables.engine  
FROM information_schema.tables  LEFT JOIN (     
   SELECT table_schema , table_name
   FROM information_schema.statistics
   GROUP BY table_schema, table_name, index_name 
   HAVING SUM( 
     case when non_unique = 0 and nullable != 'YES' then 1 else 0 end ) = count(*) ) puks   
   ON tables.table_schema = puks.table_schema 
  AND tables.table_name = puks.table_name   
  WHERE puks.table_name IS null 
  AND tables.table_type = 'BASE TABLE' 
  AND Engine="InnoDB";
+--------------+--------------+--------+
| TABLE_SCHEMA | TABLE_NAME   | ENGINE |
+--------------+--------------+--------+
| test         | table2       | InnoDB |
+--------------+--------------+--------+

您也可以使用MySQL Shell中的校驗插件:https://github.com/lefred/mysqlshell-plugins/wiki/check#getinnodbtableswithnopk

讓我們查看錶定義:

show create table table2\\G
*************** 1. row ***************
       Table: table2
Create Table: CREATE TABLE table2 (
  name varchar(20) DEFAULT NULL,
  age int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

其中的數據:

select * from table2;
+--------+-----+
| name   | age |
+--------+-----+
| mysql  |  25 |
| kenny  |  35 |
| lefred |  44 |
+--------+-----+

現在添加指定不可見主鍵:

alter table table2 
     add column id int unsigned auto_increment 
     primary key invisible first;

插入一條新記錄:

insert into table2 (name, age) values ('PHP', 25);

select * from table2;
+--------+-----+
| name   | age |
+--------+-----+
| mysql  |  25 |
| kenny  |  35 |
| lefred |  44 |
| PHP    |  25 |
+--------+-----+

如果我們想要查看主鍵:

select id, table2.* from table2;
+----+--------+-----+
| id | name   | age |
+----+--------+-----+
|  1 | mysql  |  25 |
|  2 | kenny  |  35 |
|  3 | lefred |  44 |
|  4 | PHP    |  25 |
+----+--------+-----+

總結

現在您知道InnoDB中爲什麼主鍵很重要,爲什麼一個好的主鍵更重要。

從MySQL8.0.23開始,您可以用不可見列解決沒有主鍵的表。

額外

僅爲娛樂,並說明我對使用UUID_TO_BIN(UUID()) 作爲主鍵的看法,讓我們重新使用UUID作爲不可見列重複這個例子。

alter table table2 add column id binary(16) invisible first;
alter table table2 modify column id binary(16) 
      default (UUID_TO_BIN(UUID())) invisible;
update table2 set id=uuid_to_bin(uuid());
alter table table2 add primary key(id);

到目前還沒什麼特別的,只是創建不可見主鍵需要一些技巧。

查詢:

select * from table2;
+--------+-----+
| name   | age |
+--------+-----+
| mysql  |  25 |
| kenny  |  35 |
| lefred |  44 |
+--------+-----+

現在,我們再向這個表插入一條新數據:

insert into table2 (name, age) values ('PHP', 25);

select * from table2;
+--------+-----+
| name   | age |
+--------+-----+
| PHP    |  25 |
| mysql  |  25 |
| kenny  |  35 |
| lefred |  44 |
+--------+-----+

Mmmm...爲什麼PHP現在是第一行?

因爲uuid() 並不連續...

select bin_to_uuid(id), table2.* from table2;
+--------------------------------------+--------+-----+
| bin_to_uuid(id)                      | name   | age |
+--------------------------------------+--------+-----+
| 05aedcbd-5b36-11eb-94c0-c8e0eb374015 | PHP    |  25 |
| af2002e8-5b35-11eb-94c0-c8e0eb374015 | mysql  |  25 |
| af20117a-5b35-11eb-94c0-c8e0eb374015 | kenny  |  35 |
| af201296-5b35-11eb-94c0-c8e0eb374015 | lefred |  44 |
+--------------------------------------+--------+-----+

我們還有別的選擇嗎?

是的,如果我們參考官檔,我們可以使用uuid_to_bin() 函數。

alter table table2 add column id binary(16) invisible first;
alter table table2 modify column id binary(16)        
                  default (UUID_TO_BIN(UUID(),1)) invisible;
update table2 set id=uuid_to_bin(uuid(),1);

現在我們每次插入一條新記錄,插入如期望一樣是順序的:

select bin_to_uuid(id,1), table2.* from table2;
+--------------------------------------+--------+-----+
| bin_to_uuid(id,1)                    | name   | age |
+--------------------------------------+--------+-----+
| 5b3711eb-023c-e634-94c0-c8e0eb374015 | mysql  |  25 |
| 5b3711eb-0439-e634-94c0-c8e0eb374015 | kenny  |  35 |
| 5b3711eb-0471-e634-94c0-c8e0eb374015 | lefred |  44 |
| f9f075f4-5b37-11eb-94c0-c8e0eb374015 | PHP    |  25 |
| 60ccffda-5b38-11eb-94c0-c8e0eb374015 | PHP8   |   1 |
| 9385cc6a-5b38-11eb-94c0-c8e0eb374015 | Python |  20 |
+--------------------------------------+--------+-----+

我們之前看了從MySQL8.0.23後,新的不可見列的功能。如果主鍵沒有定義,我們如何使用它爲InnoDB表添加主鍵。

如之前所述,好的主鍵對InnoDB很重要(存儲,IOPS,二級索引,內存等)但是MySQL中主鍵還有一個重要的作用:複製!

異步複製

當使用"傳統複製"時,如果您修改了一行記錄(更新和刪除),那麼要在副本上修改的記錄將使用索引來標識,當然如果有主鍵的話,還會使用主鍵。InnoDB自動生成的隱藏全局6字節主鍵永遠不會被使用,因爲它是全局的,所以不能保證源和副本之間是相同的。你根本不應該考慮它。

如果算法不能找到合適的索引,或者只能找到一個非唯一索引或者包含null值,則需要使用哈希表來識別表記錄。該算法創建一個哈希表,其中包含更新或者刪除操作的記錄,並用鍵作爲該行之前完整的映像。然後,該算法遍歷目標表中的所有記錄,如果找到了所選索引,則使用該索引,否則執行全表掃描(參見官檔)。

因此,如果應用程序不支持使用額外的鍵作爲主鍵,則使用隱藏列作爲主鍵是加快複製的一個方法。

mysql> create table t1 (name varchar(20), age int);

mysql> insert into t1 values ('mysql',25),('kenny', 35),('lefred', 44);

現在添加一個自增列作爲主鍵:

mysql> alter table t1 add id int auto_increment primary key first;

然後按照應用程序中指定的INSERT語句添加一條記錄:

mysql > insert into t1 values ('python',20);
ERROR: 1136: Column count doesn't match value count at row 1

最好的方法是修改應用的INSERT 語句,但是可能嗎?

多少應用程序仍然是使用SELECT * ,並且引用列時如col[2]?

如果是這樣,您有兩種方法:

  1. 分析所有的查詢,使用重寫查詢插件

  2. 使用不可見列

在這種情況下,選擇是容易的(至少對像我這樣的懶人說)。

mysql > alter table t1 modify id int auto_increment invisible;
mysql > insert into t1 values ('python',20);
Query OK, 1 row affected (0.0887 sec)

很簡單,不是嗎?

組複製

MySQL InnoDB Cluster使用另一種複製:Group Replication。

使用組複製的要求之一是要有一個主鍵(這就是爲什麼可以使用sql_require_primary_key)。

我們使用上例中重構表,不加主鍵,檢查該實例能否作爲InnoDB Cluster:

https://lefred.be/wp-content/uploads/2021/01/Selection_9991017-1024x561.png

提示很清楚,該表上的修改不會複製到其他節點。

添加不可見主鍵,重新檢查:

https://lefred.be/wp-content/uploads/2021/01/Selection_9991018-1024x89.png

https://lefred.be/wp-content/uploads/2021/01/Selection_9991019-1024x384.png

這意味着,如果應用程序使用的表沒有主鍵,不允許遷移到MySQL InnoDB Cluster等高可用架構中,現在多虧了不可見列,這可以做到了。

這也解決了Hadoop Hive對MySQL InnoDB Cluster的支持(參見Hive-17306)。

原文鏈接

  • 不可見列-part1 (https://lefred.be/content/mysql-invisible-column-part-i/)

  • 不可見列-part2 (https://lefred.be/content/mysql-invisible-column-part-ii/)

  • 不可見列-part3 (https://lefred.be/content/mysql-invisible-column-part-iii/)

  • HIVE-17306 (https://issues.apache.org/jira/browse/HIVE-17306)

全文完。

Enjoy MySQL 8.0 :)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章