MySQL 添加主鍵可以節省磁盤空間嗎?

MySQL 表定義主鍵不是必須的,並且直到今天(MySQL 版本 8.3.0)都是這樣。不過,在 MGR 和 PXC 架構中不允許使用沒有主鍵的表。如果數據表沒有主鍵,會有許多衆所周知的負面性能影響,其中最痛苦的是複製速度很糟糕。

今天,我想快速說明一下 需要使用主鍵的另一個原因:磁盤空間!

創建一個非常簡單的示例表:

mysql > show create table test1\G
*************************** 1. row ***************************
       Table: test1
Create Table: CREATE TABLE `test1` (
  `a` int NOT NULL,
  `b` bigint DEFAULT NULL,
  KEY `a` (`a`),
  KEY `b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

填充 10M 測試行,需要 748M 磁盤空間。現在,假設我的測試表的 a 列具有唯一值:

mysql > select count(*) from test1;
+----------+
| count(*) |
+----------+
| 10000000 |
+----------+
1 row in set (1.34 sec)

mysql > select count(DISTINCT(a)) from test1;
+--------------------+
| count(DISTINCT(a)) |
+--------------------+
|           10000000 |
+--------------------+
1 row in set (5.25 sec)

下面我將把索引類型更改爲主鍵:

mysql > alter table test1 add primary key(a), drop key a;
Query OK, 0 rows affected (48.90 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql > show create table test1\G
*************************** 1. row ***************************
       Table: test1
Create Table: CREATE TABLE `test1` (
  `a` int NOT NULL,
  `b` bigint DEFAULT NULL,
  PRIMARY KEY (`a`),
  KEY `b` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

結果,該表被重新創建,其磁盤大小減少到 588M,非常顯着!

爲什麼會發生這種情況?我們擁有完全相同的數據,並且在兩種情況下都對兩列都建立了索引!讓我們檢查一下更改前後該表的更多詳細信息。

之前,在沒有主鍵的情況下,當兩列都通過輔助鍵建立索引時,我們可以看到以下內容:

mysql > select SPACE,INDEX_ID,i.NAME as index_name, t.NAME as table_name, CLUST_INDEX_SIZE, OTHER_INDEX_SIZE from information_schema.INNODB_INDEXES i JOIN information_schema.INNODB_TABLESPACES t USING(space) JOIN information_schema.INNODB_TABLESTATS ts WHERE t.NAME=ts.NAME AND t.NAME='db1/test1'\G
*************************** 1. row ***************************
           SPACE: 50
        INDEX_ID: 232
      index_name: a
      table_name: db1/test1
CLUST_INDEX_SIZE: 24699
OTHER_INDEX_SIZE: 22242
*************************** 2. row ***************************
           SPACE: 50
        INDEX_ID: 231
      index_name: b
      table_name: db1/test1
CLUST_INDEX_SIZE: 24699
OTHER_INDEX_SIZE: 22242
*************************** 3. row ***************************
           SPACE: 50
        INDEX_ID: 230
      index_name: GEN_CLUST_INDEX
      table_name: db1/test1
CLUST_INDEX_SIZE: 24699
OTHER_INDEX_SIZE: 22242
3 rows in set (0.00 sec)

竟然還有第三個索引!通過 innodb_ruby 工具可以更詳細地查看每個索引,可以看到它的大小是最大的(id=230):

$ innodb_space -f msb_8_3_0/data/db1/test1.ibd space-indexes
id      name  root        fseg        fseg_id     used        allocated   fill_factor
230           4           internal    3           27          27          100.00%    
230           4           leaf        4           24634       24672       99.85%      
231           5           internal    5           21          21          100.00%    
231           5           leaf        6           12627       12640       99.90%      
232           6           internal    7           13          13          100.00%    
232           6           leaf        8           9545        9568        99.76%

這就是 InnoDB 引擎的工作原理;如果沒有定義明確的主鍵,它將添加一個名爲 的內部主鍵 GEN_CLUST_INDEX。由於它包含整個數據行,因此其大小開銷非常大。

將二級索引替換爲顯式主鍵後,就不再需要隱藏索引了。因此,我們總共剩下兩個索引:

mysql > select SPACE,INDEX_ID,i.NAME as index_name, t.NAME as table_name, CLUST_INDEX_SIZE,OTHER_INDEX_SIZE from information_schema.INNODB_INDEXES i JOIN information_schema.INNODB_TABLESPACES t USING(space) JOIN information_schema.INNODB_TABLESTATS ts WHERE t.NAME=ts.NAME AND t.NAME='db1/test1'\G
*************************** 1. row ***************************
           SPACE: 54
        INDEX_ID: 237
      index_name: b
      table_name: db1/test1
CLUST_INDEX_SIZE: 23733
OTHER_INDEX_SIZE: 13041
*************************** 2. row ***************************
           SPACE: 54
        INDEX_ID: 236
      index_name: PRIMARY
      table_name: db1/test1
CLUST_INDEX_SIZE: 23733
OTHER_INDEX_SIZE: 13041
2 rows in set (0.01 sec)
$ innodb_space -f msb_8_3_0/data/db1/test1.ibd space-indexes
id      name  root        fseg        fseg_id     used        allocated   fill_factor
236           4           internal    3           21          21          100.00%    
236           4           leaf        4           20704       23712       87.31%      
237           5           internal    5           17          17          100.00%    
237           5           leaf        6           11394       13024       87.48%

GEN_CLUST_INDEX vs GIPK

每個 InnoDB 表都有一個聚集鍵,因此不定義聚集鍵不會節省任何磁盤空間,有時甚至相反,如上所示。因此,即使有問題的表中沒有任何現有列是唯一的,最好還是添加另一個唯一列作爲主鍵。內部 GEN_CLUST_INDEX 不暴露給 MySQL 上層,只有 InnoDB 引擎知道它,因此對於複製速度來說沒有用處。因此,顯式主鍵始終是更好的解決方案。

但是,如果由於遺留應用程序問題而無法添加新的主鍵列,建議使用不可見的主鍵(GIPK)來當作主鍵。這樣,您將獲得性能優勢,同時對應用程序是不可見的。

mysql > set sql_require_primary_key=1;
Query OK, 0 rows affected (0.00 sec)

mysql > create table nopk (a int);
ERROR 3750 (HY000): Unable to create or change a table without a primary key, when the system variable 'sql_require_primary_key' is set. Add a primary key to the table or unset this variable to avoid this message. Note that tables without a primary key can cause performance problems in row-based replication, so please consult your DBA before changing this setting.

mysql > set sql_generate_invisible_primary_key=1;
Query OK, 0 rows affected (0.00 sec)

mysql > create table nopk (a int);
Query OK, 0 rows affected (0.02 sec)

mysql > show create table nopk\G
*************************** 1. row ***************************
       Table: nopk
Create Table: CREATE TABLE `nopk` (
  `my_row_id` bigint unsigned NOT NULL AUTO_INCREMENT /*!80023 INVISIBLE */,
  `a` int DEFAULT NULL,
  PRIMARY KEY (`my_row_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

mysql > select * from nopk;
+------+
| a    |
+------+
|  100 |
+------+
1 row in set (0.00 sec)

因此,我們的應用程序根本不知道新列。但如果需要,我們仍然可以使用它,例如,輕鬆地將表讀取或寫入分成可預測的塊:

mysql > select my_row_id,a from nopk;
+-----------+------+
| my_row_id | a    |
+-----------+------+
|         1 |  100 |
+-----------+------+
1 row in set (0.00 sec)

請注意,對於缺少主鍵的架構,在強制執行變量 sql_require_primary_key 之前,最好首先啓用 sql_generate_invisible_primary_key 並使用邏輯備份和恢復重新創建數據。簡單的表優化不會增加不可見主鍵。無論如何,對於遺留的應用來說,擁有不可見主鍵(GIPK)應該是一個雙贏的解決方案。

更多技術文章,請訪問:https://opensource.actionsky.com/

關於 SQLE

SQLE 是一款全方位的 SQL 質量管理平臺,覆蓋開發至生產環境的 SQL 審覈和管理。支持主流的開源、商業、國產數據庫,爲開發和運維提供流程自動化能力,提升上線效率,提高數據質量。

SQLE 獲取

類型 地址
版本庫 https://github.com/actiontech/sqle
文檔 https://actiontech.github.io/sqle-docs/
發佈信息 https://github.com/actiontech/sqle/releases
數據審覈插件開發文檔 https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章