在 InnoDB 存储引擎中,表都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table),或者叫聚集索引(clustered index)。

索引组织表存在如下特性:

  • 每张表都必须有一个主键。
  • 根据主键的值构造一棵 B+Tree。
  • 这棵 B+Tree 的叶子节点(leaf page)存放所有的记录(Row)。
  • 非叶子节点(Non-leaf page)存放的主键和指针(若干个 {主键,指针} 组成一个非页节点)。
    • 这里的指针其实就是 PageNumber(这里不需要 SpaceID,因为 SpaceID 对应的是 ibd 文件,我们现在是在 ibd 文件内部查找数据)。

主键

如果创建表的时候,没有显示指定主键,则 InnoDB 会按照如下方式选择或创建主键:

  1. 判断表中是否有非空的唯一索引,如果有,该列即为主键。
    1. 如果存在多个非空唯一索引,以创建表时以第一个定义的非空唯一索引为准,而不是(columns)定义的顺序。
  2. 如果上述条件都不符合,则 InnoDB 会自动创建一个6字节大小的主键(Int 类型,全局唯一,且是隐藏列)。 ```sql mysql> create table test_key_3( -> a int, -> b int, -> c int); Query OK, 0 rows affected (0.11 sec)

mysql> insert into test_key_3 values(1,2,3),(4,5,6),(7,8,9); Query OK, 3 rows affected (0.02 sec) Records: 3 Duplicates: 0 Warnings: 0

mysql> select *,_rowid from test_key_3; ERROR 1054 (42S22): Unknown column ‘_rowid’ in ‘field list’ — 这里无法用_rowid查看,因为系统rowid对用户是透明的

  1. 假设有 a b 两张表都使用了系统定义的主键,则系统定义的主键的 ID 不是在表内进行单独递增的,而是全局递增。
  2. 该系统的 rowid 是定义在 ibdata1.ibd 中的 sys_rowid 中,全局自增6个字节表示的数据量为 2^48 ,通常意义上是够用的。
  3. 强烈建议自己显示定义主键。
  4. **如果存在多个非空的唯一索引,如何知道是哪个索引作为主键了?**
  5. ```sql
  6. mysql> create table test_key (
  7. -> a int,
  8. -> b int not null,
  9. -> c int not null,
  10. -> unique key(a),
  11. -> unique key(c),
  12. -> unique key(b)
  13. -> );
  14. Query OK, 0 rows affected (0.16 sec)
  15. mysql> insert into test_key values(1,2,3),(4,5,6),(7,8,9);
  16. Query OK, 3 rows affected (0.04 sec)
  17. Records: 3 Duplicates: 0 Warnings: 0
  18. mysql> select * from test_key;
  19. +------+---+---+
  20. | a | b | c |
  21. +------+---+---+
  22. | 1 | 2 | 3 |
  23. | 4 | 5 | 6 |
  24. | 7 | 8 | 9 |
  25. +------+---+---+
  26. 3 rows in set (0.00 sec)
  27. mysql> select *, _rowid from test_key; -- _rowid是主键值
  28. +------+---+---+--------+
  29. | a | b | c | _rowid |
  30. +------+---+---+--------+
  31. | 1 | 2 | 3 | 3 | -- 可以发现,这里的主键是c
  32. | 4 | 5 | 6 | 6 |
  33. | 7 | 8 | 9 | 9 |
  34. +------+---+---+--------+
  35. 3 rows in set (0.00 sec)
  36. mysql> create table test_key_2 (
  37. -> a varchar(8), -- 使用varchar类型
  38. -> b varchar(8) not null,
  39. -> c varchar(8) not null,
  40. -> unique key(a),
  41. -> unique key(c),
  42. -> unique key(b)
  43. -> );
  44. Query OK, 0 rows affected (0.15 sec)
  45. mysql> insert into test_key_2 values('a','b','c'),('d','e','f'),('g','h','i');
  46. Query OK, 3 rows affected (0.02 sec)
  47. Records: 3 Duplicates: 0 Warnings: 0
  48. mysql> select * from test_key_2;
  49. +------+---+---+
  50. | a | b | c |
  51. +------+---+---+
  52. | a | b | c |
  53. | d | e | f |
  54. | g | h | i |
  55. +------+---+---+
  56. 3 rows in set (0.00 sec)
  57. mysql> select *, _rowid from test_key_2;
  58. ERROR 1054 (42S22): Unknown column '_rowid' in 'field list' -- 报错了
  59. -- _rowid只能是在key的类型为整型时才有效
  60. -- 方法一
  61. mysql> select * from information_schema.columns where table_name="test_key_2" and column_key="PRI"\G
  62. *************************** 1. row ***************************
  63. TABLE_CATALOG: def
  64. TABLE_SCHEMA: burn_test
  65. TABLE_NAME: test_key_2
  66. COLUMN_NAME: c -- 该列的列名是 c
  67. ORDINAL_POSITION: 3
  68. COLUMN_DEFAULT: NULL
  69. IS_NULLABLE: NO
  70. DATA_TYPE: varchar
  71. CHARACTER_MAXIMUM_LENGTH: 8
  72. CHARACTER_OCTET_LENGTH: 32
  73. NUMERIC_PRECISION: NULL
  74. NUMERIC_SCALE: NULL
  75. DATETIME_PRECISION: NULL
  76. CHARACTER_SET_NAME: utf8mb4
  77. COLLATION_NAME: utf8mb4_general_ci
  78. COLUMN_TYPE: varchar(8)
  79. COLUMN_KEY: PRI -- 该列是主键
  80. EXTRA:
  81. PRIVILEGES: select,insert,update,references
  82. COLUMN_COMMENT:
  83. GENERATION_EXPRESSION:
  84. 1 row in set (0.00 sec)
  85. -- 方法二
  86. mysql> desc test_key_2;
  87. +-------+------------+------+-----+---------+-------+
  88. | Field | Type | Null | Key | Default | Extra |
  89. +-------+------------+------+-----+---------+-------+
  90. | a | varchar(8) | YES | UNI | NULL | |
  91. | b | varchar(8) | NO | UNI | NULL | |
  92. | c | varchar(8) | NO | PRI | NULL | | -- key 是PRI ,就可以知道 c 列是主键
  93. +-------+------------+------+-----+---------+-------+
  94. 3 rows in set (0.00 sec)

聚集索引一行记录存在以下系统列:

  1. rowid:B+Tree 索引键值,只能查整型的主键值,作用不大。
  2. trx id:6字节,事务 id 列。
  3. roll pointer:7字节,回滚指针列。

索引组织表 VS 堆表

堆表

image.png
堆表将索引和数据分开(如 MyISAM),索引中叶子节点存放的是数据的位置,而不是数据本身。

索引组织表

image.png
索引组织表将索引和数据放在了一起,索引的叶子节点(leaf page)存放了所有完整的记录(Row),索引即数据,数据即索引。

注意:

  1. 非叶子节点(Non-leaf page)中不会存放所有的数据(Row)的 {主键, PageNumber},而是从叶子节点(leaf page)中选出一个数据的主键,将这个主键和该页的 PageNumber 填入到非叶节点(Non-leaf page)中。
  2. 从逻辑上看,是一棵 B+Tree 树,但是从物理上看都是每个页(非叶子节点和叶子节点)通过指针串在一起,使得逻辑有序。

二级索引

image.png
二级索引中的叶子节点不存放数据本身,而是存放主键。

查询数据对比

堆表查询:
image.png
索引组织表以及二级索引查询:
image.png

Page 的空间申请

  • 叶子节点(leaf page)由 leaf page segment 进行申请空间。
  • 非叶子节点(Non-leaf page)由 Non-leaf page segment 进行申请空间。

所以索引由两个段组成:

  • leaf page segment
  • Non-leaf page segment

段(segment)是由区(extent)组成,申请空间就按照区(extent)进行申请(一般情况下一次申请4个区)。

作者:殷建卫 链接:https://www.yuque.com/yinjianwei/vyrvkf/wpbl5s 来源:殷建卫 - 架构笔记 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。