0、导读
InnoDB表的索引有哪些特性,以及索引组织结构是怎样的
1、InnoDB聚集索引特点
我们知道,InnoDB引擎的聚集索引组织表,必然会有一个聚集索引。
行数据(row data)存储在聚集索引的叶子节点(除了发生overflow的列,参见 浅析InnoDB Record Header及page overflow,后面简称 “前置文”),并且其存储的相对顺序取决于聚集索引的顺序。这里说相对顺序而不是物理顺序,是因为叶子节点数据页中,行数据的物理顺序和相对顺序可能并不是一致的,放在后面会讲。
InnoDB聚集索引的选择先后顺序是这样的:
- 如果有显式定义的主键(PRIMARY KEY),则会选择该主键作为聚集索引
- 否则,选择第一个所有列都不允许为NULL的唯一索引
- 若前两者都没有,则InnoDB会选择内置的DB_ROW_ID作为聚集索引,命名为GEN_CLUST_INDEX
特别提醒: DB_ROW_ID占用6个字节,每次自增,且是整个实例内全局分配。也就是说,当前实例如果有多个表都采用了内置的DB_ROW_ID作为聚集索引,则在这些表插入新数据时,他们的内置DB_ROW_ID值并不是连续的,而是跳跃的。像下面这样:
t1表的ROW_ID:1、3、7、10 t2表的ROW_ID:2、4、5、6、8、9
2、InnoDB索引结构
InnoDB默认的索引数据结构采用B+树(空间索引采用R树),索引数据存储在叶子节点。
InnoDB的基本I/O存储单位是数据页(page),一个page默认是16KB。我们在 前置文 说过,每个page默认会预留1/16空闲空间用于后续数据“变长”更新所需,因此在最理想的顺序插入状态下,其产生的碎片也最少,这时候差不多能填满15/16的page空间。如果是随机写入的话,则page空间利用率大概是1/2 ~ 15/16。
当 row_format = DYNAMIC|COMPRESSED 时,索引最多长度为 3072字节,当 row_format = REDUNDANT|COMPACT 时,索引最大长度为 767字节。当page size不是默认的16KB时,最大索引长度限制也会跟着发生变化。
我们接下来分别验证关于InnoDB索引的基本结构特点。
首先创建如下测试表:
[root@yejr.me] [innodb]> CREATE TABLE `t1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `c1` int(10) unsigned NOT NULL DEFAULT '0', `c2` varchar(100) NOT NULL, `c3` varchar(100) NOT NULL, PRIMARY KEY (`id`), KEY `c1` (`c1`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
用下面的方法写入10条测试数据:
set @uuid1=uuid(); set @uuid2=uuid(); insert into t1 select 0, round(rand()*1024), @uuid1, concat(@uuid1, @uuid2);
看下 t1 表的整体结构:
可以看到
索引ID | 索引类型 | 根节点page no | 索引层高 |
238 | 主键索引(聚集索引) | 3 | 1 |
239 | 辅助索引 | 4 | 1 |
3、InnoDB索引特点验证
3.1 特点1:聚集索引叶子节点存储整行数据
先扫描第3个page,截取其中第一条物理记录的内容:
[root@yejr.me]# innodb_space -s ibdata1 -T innodb/t1 -p 3 page-dump ... records: {:format=>:compact, :offset=>127, :header=> {:next=>263, :type=>:conventional, :heap_number=>2, :n_owned=>0, :min_rec=>false, :deleted=>false, :nulls=>[], :lengths=>{"c2"=>36, "c3"=>72}, :externs=>[], :length=>7}, :next=>263, :type=>:clustered, #第一条物理记录,id=1 :key=>[{:name=>"id", :type=>"INT UNSIGNED", :value=>1}], :row=> [{:name=>"c1", :type=>"INT UNSIGNED", :value=>777}, {:name=>"c2", :type=>"VARCHAR(400)", :value=>"a1c1a7c7-bda5-11e9-8476-0050568bba82"}, {:name=>"c3", :type=>"VARCHAR(400)", :value=> "a1c1a7c7-bda5-11e9-8476-0050568bba82a1c1aec5-bda5-11e9-8476-0050568bba82"}], :sys=> [{:name=>"DB_TRX_ID", :type=>"TRX_ID", :value=>10950}, {:name=>"DB_ROLL_PTR", :type=>"ROLL_PTR", :value=> {:is_insert=>true, :rseg_id=>119, :undo_log=>{:page=>469, :offset=>272}}}], :length=>129, :transaction_id=>10950, :roll_pointer=> {:is_insert=>true, :rseg_id=>119, :undo_log=>{:page=>469, :offset=>272}}}
很明显,的确是存储了整条数据的内容。
聚集索引树的键值(key)是主键索引值(i=10),聚集索引节点值(value)是其他非聚集索引列(c1,c2,c3)以及隐含列(DB_TRX_ID、DB_ROLL_PTR)。
优化建议1:尽量不要存储大对象数据,使得每个叶子节点都能存储更多数据,降低碎片率,提高buffer pool利用率。此外也能尽量避免发生overflow。