MySQL 中可以通过添加主键来节省磁盘空间吗?(译文)

server/2025/2/12 12:03:35/

从历史上看,MySQL 不需要在表上定义显式主键,直到今天默认都是这样。不过,这种要求是通过两种复制方法施加的:组复制和 Percona XtraDB 集群 (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)

将 (secondary) 索引类型更改为 primary:

mysql > alter table test1 add primary key(a), drop key a;
Query OK, 0 rows affected (48.90 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql > 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: 50INDEX_ID: 232index_name: atable_name: db1/test1
CLUST_INDEX_SIZE: 24699
OTHER_INDEX_SIZE: 22242
*************************** 2. row ***************************SPACE: 50INDEX_ID: 231index_name: btable_name: db1/test1
CLUST_INDEX_SIZE: 24699
OTHER_INDEX_SIZE: 22242
*************************** 3. row ***************************SPACE: 50INDEX_ID: 230index_name: GEN_CLUST_INDEXtable_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 引擎的工作原理;如果未定义显式 PK,它将添加一个名为 .由于它包含整个数据行,因此其大小开销很大。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: 54INDEX_ID: 237index_name: btable_name: db1/test1
CLUST_INDEX_SIZE: 23733
OTHER_INDEX_SIZE: 13041
*************************** 2. row ***************************SPACE: 54INDEX_ID: 236index_name: PRIMARYtable_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) 与生成的不可见主键 (GIPK

每个 InnoDB table 都有一个集群键,因此不定义一个不会节省任何磁盘空间,有时恰恰相反,如上所述。因此,即使有问题的表的现有列都不是唯一的,最好还是添加另一个唯一列作为主键。内部的没有暴露在上层 MySQL 层,只有 InnoDB 引擎知道它,因此它对复制速度毫无用处。因此,显式 PK 始终是更好的解决方案。

但是,如果由于遗留应用程序问题而无法添加新的 PK 列,则仍应使用不可见的主键来强制实施主键。这样,您将获得性能优势,同时,更改对应用程序是透明的。GEN_CLUST_INDEX

让我们看看它在实践中是如何工作的:

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并使用逻辑转储和还原重新创建数据。简单的表优化不会添加不可见的 PK。无论如何,拥有一个不可见的 PK 应该是遗留应用程序的双赢解决方案。

总结一下:

  • 可能值得检查一下更改索引类型是否可以节省磁盘空间!
  • 如果由于应用程序限制而无法添加主键,请考虑使用不可见的主键!

http://www.ppmy.cn/server/167048.html

相关文章

独立站赋能反向海淘:跨境代购系统的用户体验与支付解决方案

随着全球化的推进以及消费者对海外商品多样化需求的增长,独立站赋能的反向海淘模式愈发火热,其中跨境代购系统的用户体验与支付解决方案起着关键作用。 一、跨境代购系统的用户体验 界面友好性 独立站的页面设计需要简洁、直观,方便用户快速…

innovus如何分步长func和dft时钟

在Innovus工具中,分步处理功能时钟(func clock)和DFT时钟(如扫描测试时钟)需要结合设计模式(Function Mode和DFT Mode)进行约束定义、时钟树综合(CTS)和时序分析。跟随分…

.net framework 4.5 的项目,用Mono 部署在linux

步骤 1:安装 Mono 更新包列表: 首先,更新 Ubuntu 的包列表以确保获取最新的软件包信息。 sudo apt update 安装 Mono: 安装 Mono 完整版(mono-complete),它包含了运行 .NET 应用程序所需的所有…

1.4 AOP编程范式

1.4 AOP编程范式 1.4.1 代理模式底层原理剖析(字节码级解析) 代理机制对比矩阵: | 维度 | JDK动态代理 | CGLIB字节码增强 | |----------------|--------------------------------|---------…

SAP-SD信用管理实施总结

SAP-SD 信用管理实施总结 摘要 信用管理是SAP-ERP系统(以下简称SAP) SD模块中的子模块,主要用于有效平衡赊销模式和资金回笼这两个业务层面之间的矛盾。本文档以信用管理在SAP系统中的实现方法为出发点,结合业务背景,以…

C语言简单练习题

文章目录 练习题一、计算n的阶乘bool类型 二、计算1!2!3!...10!三、计算数组arr中的元素个数二分法查找 四、动态打印字符Sleep()ms延时函数system("cls")清屏函数 五、模拟用户登录strcmp()函数 六、猜数字小游戏产生一个随机数randsrandRAND_MAX时间戳time() 示例 …

Deepseek使用途径以及Prompt 提示词编写原理

Deepseek使用途径以及Prompt 提示词编写原理 1.Deepseek使⽤途径 1.官⽹及APP ⽹址: deepseek.com 及移动应⽤(iOS/Android) 特征:完整版R1模型,⽀持深度搜索,但⽬前因流量⼤常遇到服务器繁忙问题。 2.…

PCM与G711A互转

PCM与G711A互转 工具类(Java)调用方法(Kotlin) 工具类(Java) public class G711Code {private final static int SIGN_BIT 0x80;private final static int QUANT_MASK 0xf;private final static int SEG…