一. 表空间内部组织结构

表空间
内部有多个段对象(Segment) 组成
每个段(Segment)由区(Extent) 组成
每个区(Extent)由页(Page)组成
每个页里面报存数据 (或者叫记录 Row)

|

1.段对用户来说是透明的

2.段也是一个 逻辑概念

3.目前为止在 information_schema 中无法找到段的概念

4.重点需要理解区(Extent) 和页(Page) 的概念

1.1表空间 – 区

  • 区是最小的空间申请单位,区的固定大小1M

    • innodb设置为 page_size= 16K, 那么一个区 1M * 1024 / 16 = 64 个页
    • 同理 page_size= 8K 就是 128个页
    • 同理 page_size= 4K 就是 256个页
  • 通常说来,一次申请4个区(4M) 的大小(存在一次申请5个区的时候,但是绝大部分情况就是申请4个区)

  • 单个区的1M空间内,物理上是连续 的(一次申请的4个区的空间之间(1M和1M之间)不保证连续)

1.2 空间 – 页

1.2.1 页的定义

  • 页是最小的I/O操作单位
  • data的最小单位不是页,而是页中的 记录(row)
  • 普通用户表中MySQL默认的每个页为 16K
  • 从MySQL5.6开始使用 innodb_page_size 可以控制页大小
  • 一旦数据库通过 innodb_page_size 创建完成,则后续无法更改
  • innodb_page_size 是针对 普通表 的, 压缩表 不受其限制

1.2.2 如何定位到页

  • 每个 表空间 都对应 一个 SpaceID ,而 表空间 又对应一个 ibd文件 ,那么一个 ibd文件 也对应一个 SpaceID

  • 因为 表空间 <--> idb文件表空间 <--> SpaceID ,所以 ibd文件 <--> SpaceID

  • ibdata1 对应的 SpaceID 为 0

  • 每创建一个表空间(ibd文件) , SpaceID 自增长(全局)

  • PageNumber

    • 在一个表空间中,第几个16K的页(假设 innodb_page_size = 16K) 即为 PageNumber

每个表空间中,都是从0开始递增,且仅仅是表空间内唯一

每次读取 Page 时,都是通过 SpaceIDPageNumber 进行读取;

可以简单理解为从表空间的开头读多少个 PageNumber * PageSize 的字节(偏移量)

想像成数组,数组的名字就是 SpaceID, 数组的下标就是PageNumber

1).可以通过( SpaceID , PageNumber )定位到某一个页

2).在一个SpaceID(ibd文件)中, PageNumber 是唯一且自增

3).这里的区(extent) 的概念已经弱化。在这个例子中,第一个区的PageNumber是(0~63) 且这 64个页在物理上是连续 的;第二个区的PageNumber是(64~127) 且这 64个页在物理上也是连续 的;但是(0~63) 和(64~127)之间 在物理上则不一定是连续的 ,因为区和区之间在物理上不一定是连续的。

4).删除表的时候, SpaceID不会回收 ,SpaceID是全局自增长的。

  1. mysql> select * from information_schema.innodb_sys_tables limit 1\G ; -- INNODB_SYS_TABLES
  2. *************************** 1. row ***************************
  3. TABLE_ID: 14
  4. NAME: SYS_DATAFILES
  5. FLAG: 0
  6. N_COLS: 5
  7. SPACE: 0
  8. FILE_FORMAT: Antelope
  9. ROW_FORMAT: Redundant
  10. ZIP_PAGE_SIZE: 0
  11. SPACE_TYPE: System
  12. 1 row in set (0.01 sec)
  13. mysql>select name, space,table_id from information_schema.innodb_sys_tables where space=0 ;
  14. +------------------+-------+----------+
  15. | name | space | table_id |
  16. +------------------+-------+----------+
  17. | SYS_DATAFILES | 0 | 14 |
  18. | SYS_FOREIGN | 0 | 11 |
  19. | SYS_FOREIGN_COLS | 0 | 12 |
  20. | SYS_TABLESPACES | 0 | 13 |
  21. | SYS_VIRTUAL | 0 | 15 |
  22. +------------------+-------+----------+
  23. 5 rows in set (0.01 sec)
  24. mysql> select name, space,table_id from information_schema.innodb_sys_tables where space<>0 limit 5 ;
  25. +-----------------------------+-------+----------+
  26. | name | space | table_id |
  27. +-----------------------------+-------+----------+
  28. | employees/departments | 32 | 47 |
  29. | employees/dept_emp | 34 | 49 |
  30. | employees/dept_manager | 33 | 48 |
  31. | employees/employee_comps_1 | 48 | 63 |
  32. | employees/employee_comps_2k | 50 | 65 |
  33. +-----------------------------+-------+----------+
  34. 5 rows in set (0.00 sec)
  35. -- 独立表空间的table_id SpaceID 一一对应
  36. -- 共享表空间是多个table_id 对应 一个 SpaceID

1.3 压缩表

官方文档

  • 基于页的压缩,每个表的页大小可以不同(针对压缩表来讲)
  1. -- ger3_spacefile_block_size=4096,不是innodb_page_size的大小
  2. -- 所在在创建 普通表 的时候,报错了
  3. mysql> create table test_ger (a int) tablespace=ger3_space;
  4. ERROR 1478 (HY000): InnoDB: Tablespace `ger3_space` uses block size 4096 and cannot contain a table with physical page size 8192
  5. -- 使用压缩表的方式
  6. mysql> create table comps_test1 (a int) row_format=compressed, key_block_size=4; -- 1K, 2K, 4K, 8K, 16K 只有这几个页大小可以选择
  7. Query OK, 0 rows affected (0.13 sec)
  8. -- 在之前的ger3_space中创建压缩表
  9. mysql> create table comps_test2 (a int)tablespace=ger3_space row_format=compressed, key_block_size=4;
  10. -- 由于ger3_space4K的,所以这里页大小也只能是4K Query OK, 0 rows affected (0.09 sec)
  11. -- 修改存在的表变成压缩表
  12. mysql> alter table t1 row_format=compressed,key_block_size=4; Query OK, 0 rows affected (0.17 sec)
  13. Records: 0 Duplicates: 0 Warnings: 0

注意:

虽然SQL语法中写的是 row_format=compressed ,但是压缩是针对 的,而不是记录 ;即读页 的时候解压 ,写页 的时候压缩 ,并不会在读取或写入单个记录(row)时就进行解压或压缩操作。

1.3.1 key_block_size的含义

  • key_block_size 的可选项是1k,2k,4k,8k,16k(是页大小,不是比例)

  • 不是将原来 innodb_page_size页大小的数据压缩成 key_block_size 的页大小,因为有些数据可能不能压缩,或者压缩不到那么小

  • 压缩是将原来的页的数据通过压缩算法压缩到一定的大小,然后用 key_block_size大小的页去存放。

比如原来的 innodb_page_size 大小是 16K ,现在的 key_block_size 设置为 8K ,某表的数据大小是 24k ,原先使用 2 个 16k 的页存放;压缩后,数据从 24k –> 18k ;由于现在的 key_block_size=8k 所以需要 3 个 8K 的页存放压缩后的 18k 数据多余的空间可以留给下次插入或者更新

压缩比和设置的 key_block_size 没有关系。压缩比看数据本身和算法(zlib), key_block_size 仅仅是设置存放压缩数据的页大小,不解压也能插入数据,通过在剩余空间直接存放 redo log ,然后页空间存放满后,再解压,利用 redo log 更新完成后,最后再压缩存放(此时就没有redo log 了)。减少解压和压缩的次数。

  • 查看压缩比
  1. mysql> use employees ;
  2. Database changed
  3. mysql> create table employee_comps_1 like employees;
  4. Query OK, 0 rows affected
  5. mysql> alter table employee_comps_1 row_format=compressed,key_block_size=4;
  6. Query OK, 0 rows affected
  7. mysql> > show create table employee_comps_1\G
  8. *************************** 1. row ***************************
  9. Table: employee_comps_1
  10. Create Table: CREATE TABLE `employee_comps_1` (
  11. `emp_no` int(11) NOT NULL,
  12. `birth_date` date NOT NULL,
  13. `first_name` varchar(14) NOT NULL,
  14. `last_name` varchar(16) NOT NULL,
  15. `gender` enum('M','F') NOT NULL,
  16. `hire_date` date NOT NULL,
  17. PRIMARY KEY (`emp_no`)
  18. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=4
  19. 1 row in set (0.00 sec)
  20. -- 插入数据
  21. mysql> insert into employee_comps_1 select * from employees;
  22. Query OK, 300024 rows affected (6.65 sec)
  23. Records: 300024 Duplicates: 0 Warnings: 0
  24. -- 查看压缩比
  25. mysql> use information_schema;
  26. Database changed
  27. mysql>select * from INNODB_CMP;
  28. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  29. | page_size | compress_ops | compress_ops_ok | compress_time | uncompress_ops | uncompress_time |
  30. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  31. | 1024 | 0 | 0 | 0 | 0 | 0 |
  32. | 2048 | 0 | 0 | 0 | 0 | 0 |
  33. | 4096 | 18599 | 13442 | 2 | 5157 | 0 |
  34. | 8192 | 0 | 0 | 0 | 0 | 0 |
  35. | 16384 | 0 | 0 | 0 | 0 | 0 |
  36. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  37. 5 rows in set (0.01 sec)
  38. mysql> select 13442/18599; -- compress_ops_ok / compress_ops
  39. +-------------+
  40. | 13442/18599 |
  41. +-------------+
  42. | 0.7227 | -- 压缩比在90%
  43. +-------------+
  44. 1 row in set (0.00 sec)
  45. mysql> select * from INNODB_CMP_RESET;
  46. -- 查询INOODB_CMP_RESET,会把INNODB_CMP表中的数据复制过来,并清空INNODB_CMP
  47. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  48. | page_size | compress_ops | compress_ops_ok | compress_time | uncompress_ops | uncompress_time |
  49. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  50. | 1024 | 0 | 0 | 0 | 0 | 0 |
  51. | 2048 | 0 | 0 | 0 | 0 | 0 |
  52. | 4096 | 18599 | 13442 | 2 | 5157 | 0 |
  53. | 8192 | 0 | 0 | 0 | 0 | 0 |
  54. | 16384 | 0 | 0 | 0 | 0 | 0 |
  55. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  56. 5 rows in set (0.00 sec)
  57. mysql> select * from INNODB_CMP; -- 查询该表,数据已经被清空了
  58. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  59. | page_size | compress_ops | compress_ops_ok | compress_time | uncompress_ops | uncompress_time |
  60. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  61. | 1024 | 0 | 0 | 0 | 0 | 0 |
  62. | 2048 | 0 | 0 | 0 | 0 | 0 |
  63. | 4096 | 0 | 0 | 0 | 0 | 0 |
  64. | 8192 | 0 | 0 | 0 | 0 | 0 |
  65. | 16384 | 0 | 0 | 0 | 0 | 0 |
  66. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  67. 5 rows in set (0.00 sec)
  68. -- 注意,这个表里面的数据是累加的,是全局信息,没法对应到某一张表
  1. shell> ll -h employee*.ibd # 可以看出磁盘占用还是有明显减小的
  2. -rw-r-----. 1 mysql mysql 14M Jan 4 13:41 employee_comps_1.ibd
  3. -rw-r-----. 1 mysql mysql 22M Dec 2 21:32 employees.ibd
  1. mysql> show variables like "%innodb_cmp_per_index%";
  2. +------------------------------+-------+
  3. | Variable_name | Value |
  4. +------------------------------+-------+
  5. | innodb_cmp_per_index_enabled | OFF | -- 该功能目前是关闭的
  6. +------------------------------+-------+
  7. 1 row in set (0.02 sec)
  8. mysql> set global innodb_cmp_per_index_enabled=1;
  9. Query OK, 0 rows affected (0.00 sec)
  10. mysql> show variables like "%innodb_cmp_per_index%";
  11. +------------------------------+-------+
  12. | Variable_name | Value |
  13. +------------------------------+-------+
  14. | innodb_cmp_per_index_enabled | ON |
  15. +------------------------------+-------+
  16. 1 row in set (0.01 sec)
  17. mysql> use employees
  18. Database changed
  19. mysql> create table employee_comps_2k like employees;
  20. Query OK, 0 rows affected (0.13 sec)
  21. mysql> alter table employee_comps_2k row_format=compressed,key_block_size=2; -- 设置成2K的页大小
  22. Query OK, 0 rows affected (0.18 sec)
  23. Records: 0 Duplicates: 0 Warnings: 0
  24. mysql> insert into employee_comps_2k select * from employees; -- 插入数据
  25. Query OK, 300024 rows affected (9.68 sec)
  26. Records: 300024 Duplicates: 0 Warnings: 0
  27. mysql> use information_schema;
  28. Database changed
  29. mysql> select * from INNODB_CMP;
  30. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  31. | page_size | compress_ops | compress_ops_ok | compress_time | uncompress_ops | uncompress_time |
  32. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  33. | 1024 | 0 | 0 | 0 | 0 | 0 |
  34. | 2048 | 34676 | 23729 | 2 | 10947 | 0 |
  35. | 4096 | 0 | 0 | 0 | 0 | 0 |
  36. | 8192 | 0 | 0 | 0 | 0 | 0 |
  37. | 16384 | 0 | 0 | 0 | 0 | 0 |
  38. +-----------+--------------+-----------------+---------------+----------------+-----------------+
  39. 5 rows in set (0.01 sec)
  40. mysql> select 23729/34676;
  41. +-------------+
  42. | 23729/34676 |
  43. +-------------+
  44. | 0.6843 | -- 2K时,压缩比是68%
  45. +-------------+
  46. 1 row in set (0.01 sec)
  47. mysql> select * from INNODB_CMP_PER_INDEX; -- 开启innodb_cmp_per_index_enabled才有数据
  48. +---------------+-------------------+------------+--------------+-----------------+---------------+----------------+-----------------+
  49. | database_name | table_name | index_name | compress_ops | compress_ops_ok | compress_time | uncompress_ops | uncompress_time |
  50. +---------------+-------------------+------------+--------------+-----------------+---------------+----------------+-----------------+
  51. | employees | employee_comps_2k | PRIMARY | 34676 | 23729 | 2 | 10947 | 0 |
  52. +---------------+-------------------+------------+--------------+-----------------+---------------+----------------+-----------------+
  53. 1 row in set (0.00 sec)
  54. -- 可以看到employees.employee_comps_2k这个表的 索引的压缩比(在INNODB中索引即数据);
  55. -- page_size=2K只有一个压缩表的时候,INNODB_CMPINNODB_CMP_PER_INDEX的值是一样的,并且能够知道是哪个表的情况
  • innodb_cmp_per_index_enabled 这个参数默认关闭,开启对性能有影响

  • key_block_size=16的含义

    • 假设innodb_page_size = 16K
    • 设置key_block_size = 16有意义
    • key_block_size的设置不影响压缩本身(只和数据本身以及zlib算法有关),只是确定压缩后的数据存放的页大小
    • 字段类型如果是varchartext等类型的数据,压缩的效果还是比较明显的
    • 设置row_format=compressed就会压缩数据, 是否压缩和设置key_block_size没有关系

      所以key_block_size=16的设置是有意义的,因为数据还是进行了压缩,压缩后的数据存放在16K大小的页中

1.3.2 压缩后的存储以及性能

从上图可以得到如下信息:

  • innodb_page_size=16k 的的数据设置 key_block_size=16 是可以压缩的,且效果比较明显;
  • 并不是 key_block_size 设置的越小,压缩率就越高,上图中 8K 和 4K 的压缩率几乎一样;
  • 在启用压缩后, 16K 和 8K 的插入性能 要好于 原来 未压缩 的插入性能,所以 启用了压缩,性能不一定会变差 ;
  • 在I/O Bound(IO密集型)的业务场景下,减少I/O操作的次数对性能提升比较明显。
  • key_block_size 的设置的值( 经验值 )通常为 innodb_page_size 的 1/2

    在MySQL的官方文档中,上面(包括之前谈的)都称之为 InnoDB Table Compression,其实不够准确,因为他是 基于页的压缩

1.3.3 透明表空间压缩

官方文档

透明表空间压缩 官方测试

在MySQL官方文档中, 透明表空间压缩 称为 InnoDB Page Compression 以区别于原来的 InnoDB Table Compression ,但是他们其实 都是基于页(Page)的压缩 。

透明表空间压缩 应该写成 InnoDB Transparent Page Compression 更为贴切。

1.3.3.1 透明表空间压缩的创建

  1. -- 透明表空间压缩的创建
  2. mysql> create table trans_test_1 (a int ) compression="zlib"; -- 使用zlib的压缩算法
  3. Query OK, 0 rows affected, 1 warning (0.14 sec) -- 存在warning
  4. mysql> create table trans_test_2 (a int ) compression="lz4"; -- 使用lz4的压缩算法
  5. Query OK, 0 rows affected, 1 warning (0.13 sec)
  6. mysql> alter table trans_test_2 compression="zlib";
  7. Query OK, 0 rows affected (0.05 sec)
  8. Records: 0 Duplicates: 0 Warnings: 0
  9. mysql> optimize table trans_test_2; -- 官方文档中提及如果是已存在的表,需要执行optimize table操作
  10. +------------------------+----------+----------+-------------------------------------------------------------------+
  11. | Table | Op | Msg_type | Msg_text |
  12. +------------------------+----------+----------+-------------------------------------------------------------------+
  13. | burn_test.trans_test_2 | optimize | note | Table does not support optimize, doing recreate + analyze instead |
  14. | burn_test.trans_test_2 | optimize | status | OK |
  15. +------------------------+----------+----------+-------------------------------------------------------------------+
  16. 2 rows in set, 1 warning (0.18 sec) -- 还是有warning
  17. mysql> show create table trans_test_1\G
  18. *************************** 1. row ***************************
  19. Table: trans_test_1
  20. Create Table: CREATE TABLE `trans_test_1` (
  21. `a` int(11) DEFAULT NULL
  22. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMPRESSION='zlib'
  23. 1 row in set (0.00 sec)
  24. -- zlib的压缩比更高
  25. -- lz4的速度更快
  26. 在上述创建过程中,并 没有指定页大小 ,而是使用了文件系统(filesystem)层中 稀疏文件 的特性,来达到压缩的目的。

1.3.3.2 稀疏文件

Wiki介绍

如上图所示,可以简单的理解为,文件中数据连续为0的部分不占用磁盘空间

  1. # 创建一个零时的,且数据部分全0的文件
  2. shell> dd of=sparse-file bs=1k seek=5120 count=0 # 创建5M大小,内容全为0的文件
  3. 0+0 records in
  4. 0+0 records out
  5. 0 bytes (0 B) copied, 6.7872e-05 s, 0.0 kB/s # 无数据写入到磁盘
  6. shell> ls -hl sparse-file
  7. -rw-r--r--. 1 root root 5.0M Jan 5 00:07 sparse-file # 显示该文件大小为5M
  8. shell> du --block-size=1 sparse-file # 检查文件占用多少空间
  9. 0 sparse-file # 显示占用空间为0

  1. 压缩后,原来16K的数据压缩成了4K;
  2. 剩余的12K空间用特殊的字符填充(比如说是0);
  3. 在写入文件系统时调用 Punching holes 写入,实则只写入4K的数据;
  4. 被填充的12K的空间,可以提供给后序的插入,更新等使用;
  5. 从innodb的角度看还是16K的页大小,只是文件系统知道该页只需要4K就能够存储(对innodb是透明的);
  6. SpaceID 和 PageNumber 的读取方式没有改变(细节由文件系统屏蔽);
  7. 由于文件系统的快大小是4K,所以压缩后存储的空间也是4K对齐的
  • 比如16K压缩成了 10K ,那就需要 3个4K 去存储
  1. mysql> desc information_schema.INNODB_SYS_TABLESPACES;
  2. +----------------+---------------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +----------------+---------------------+------+-----+---------+-------+
  5. | SPACE | int(11) unsigned | NO | | 0 | |
  6. | NAME | varchar(655) | NO | | | |
  7. | FLAG | int(11) unsigned | NO | | 0 | |
  8. | FILE_FORMAT | varchar(10) | YES | | NULL | |
  9. | ROW_FORMAT | varchar(22) | YES | | NULL | |
  10. | PAGE_SIZE | int(11) unsigned | NO | | 0 | |
  11. | ZIP_PAGE_SIZE | int(11) unsigned | NO | | 0 | |
  12. | SPACE_TYPE | varchar(10) | YES | | NULL | |
  13. | FS_BLOCK_SIZE | int(11) unsigned | NO | | 0 | | -- 文件系统的块大小
  14. | FILE_SIZE | bigint(21) unsigned | NO | | 0 | | -- 文件大小
  15. | ALLOCATED_SIZE | bigint(21) unsigned | NO | | 0 | | -- 文件实际分配的大小
  16. | COMPRESSION | varchar(5) | NO | | | |
  17. +----------------+---------------------+------+-----+---------+-------+
  18. 12 rows in set (0.00 sec)

1.3.3.3 系统以及文件系统的支持

  • 操作系统以及内核

    • RHEL7 kernel >= 3.10.0-123
    • Debian 7 kernel >= 3.2
    • Ubuntu 12.04LTS kernel >= 3.2
    • Ubuntu 14.0.4LTS kernel >= 3.13
    • Oracle和Suse可以参考官方文档
  • 文件系统

    • 支持Hole Punch
    • 比如XFS,EXT4,NTFS等

1.3.3.4 关于warning

  1. mysql> show warnings;
  2. +---------+------+---------------------------------------------------------------------------------------------------------------------------+
  3. | Level | Code | Message |
  4. +---------+------+---------------------------------------------------------------------------------------------------------------------------+
  5. | Warning | 138 | InnoDB: Punch hole not supported by the file system or the tablespace page size is not large enough. Compression disabled |
  6. +---------+------+---------------------------------------------------------------------------------------------------------------------------+
  7. 1 row in set (0.00 sec)
  8. -- 原因1是我的内核版本太低,不支持
  9. -- 原因2是因为general方式安装的mysql不支持透明压缩,需要自己编译
  10. -- http://bugs.mysql.com/bug.php?id=77974

二. 索引组织表

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

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

2.1 主键

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

  1. 判断表中是否有 非空的唯一索引 ,如果有,该列即为主键 ;

    • 如果存在多个非空唯一索引,以创建表时 第一个定义 的非空唯一 索引 为准,而 不是(columns)定义的顺序
  2. 如果上述条件都不符合,则InnoDB自动创建一个6字节大小的指针;

2.2 索引组织表与堆表

2.2.1 堆表

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

2.2.2 索引组织表

2.2.2.1 索引组织表

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

索引即数据,数据即索引

注意:

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

2.2.2.2 二级索引

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

2.2.3 查询数据对比

  • 堆表查询

  • 索引组织表以及二级索引查询

2.2.4 Page的空间申请

  • 叶子节点(leaf page) 由 leaf page segment 进行申请空间
  • 非叶子节点(Non-leaf page) 由 Non-leaf page segment 进行申请空间,所以索引由 两个段组成
    1. leaf page segment
    2. Non-leaf page segment

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

三. 主键测试

3.1 多个唯一非空键

  1. mysql> create table test_key (
  2. -> a int,
  3. -> b int not null,
  4. -> c int not null,
  5. -> unique key(a),
  6. -> unique key(c),
  7. -> unique key(b)
  8. -> );
  9. Query OK, 0 rows affected (0.16 sec)
  10. mysql> insert into test_key values(1,2,3),(4,5,6),(7,8,9);
  11. Query OK, 3 rows affected (0.04 sec)
  12. Records: 3 Duplicates: 0 Warnings: 0
  13. mysql> select * from test_key;
  14. +------+---+---+
  15. | a | b | c |
  16. +------+---+---+
  17. | 1 | 2 | 3 |
  18. | 4 | 5 | 6 |
  19. | 7 | 8 | 9 |
  20. +------+---+---+
  21. 3 rows in set (0.00 sec)
  22. mysql> select *, _rowid from test_key; -- _rowid是主键值
  23. +------+---+---+--------+
  24. | a | b | c | _rowid |
  25. +------+---+---+--------+
  26. | 1 | 2 | 3 | 3 | -- 可以发现,这里的主键是c
  27. | 4 | 5 | 6 | 6 |
  28. | 7 | 8 | 9 | 9 |
  29. +------+---+---+--------+
  30. 3 rows in set (0.00 sec)
  31. mysql> create table test_key_2 (
  32. -> a varchar(8), -- 使用varchar类型
  33. -> b varchar(8) not null,
  34. -> c varchar(8) not null,
  35. -> unique key(a),
  36. -> unique key(c),
  37. -> unique key(b)
  38. -> );
  39. Query OK, 0 rows affected (0.15 sec)
  40. mysql> insert into test_key_2 values('a','b','c'),('d','e','f'),('g','h','i');
  41. Query OK, 3 rows affected (0.02 sec)
  42. Records: 3 Duplicates: 0 Warnings: 0
  43. mysql> select * from test_key_2;
  44. +------+---+---+
  45. | a | b | c |
  46. +------+---+---+
  47. | a | b | c |
  48. | d | e | f |
  49. | g | h | i |
  50. +------+---+---+
  51. 3 rows in set (0.00 sec)
  52. mysql> select *, _rowid from test_key_2;
  53. ERROR 1054 (42S22): Unknown column '_rowid' in 'field list' -- 报错了
  54. -- _rowid只能是在key的类型为整型时才有效

-- 方法一

  1. mysql> select * from information_schema.columns where table_name="test_key_2" and column_key="PRI"\G
  2. *************************** 1. row ***************************
  3. TABLE_CATALOG: def
  4. TABLE_SCHEMA: burn_test
  5. TABLE_NAME: test_key_2
  6. COLUMN_NAME: c -- 该列的列名是 c
  7. ORDINAL_POSITION: 3
  8. COLUMN_DEFAULT: NULL
  9. IS_NULLABLE: NO
  10. DATA_TYPE: varchar
  11. CHARACTER_MAXIMUM_LENGTH: 8
  12. CHARACTER_OCTET_LENGTH: 32
  13. NUMERIC_PRECISION: NULL
  14. NUMERIC_SCALE: NULL
  15. DATETIME_PRECISION: NULL
  16. CHARACTER_SET_NAME: utf8mb4
  17. COLLATION_NAME: utf8mb4_general_ci
  18. COLUMN_TYPE: varchar(8)
  19. COLUMN_KEY: PRI -- 该列是主键
  20. EXTRA:
  21. PRIVILEGES: select,insert,update,references
  22. COLUMN_COMMENT:
  23. GENERATION_EXPRESSION:
  24. 1 row in set (0.00 sec)

-- 方法二

  1. mysql> desc test_key_2;
  2. +-------+------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +-------+------------+------+-----+---------+-------+
  5. | a | varchar(8) | YES | UNI | NULL | |
  6. | b | varchar(8) | NO | UNI | NULL | |
  7. | c | varchar(8) | NO | PRI | NULL | | -- key PRI ,就可以知道 c 列是主键
  8. +-------+------------+------+-----+---------+-------+
  9. 3 rows in set (0.00 sec)

3.2 系统定义主键(系统rowid)

  • 当用户表中没有显示的指定主键,且没有非空唯一键时,系统会 自定义 一个 主键 (6个字节,int型,全局,隐藏)
  1. mysql> create table test_key_3(
  2. -> a int,
  3. -> b int,
  4. -> c int);
  5. Query OK, 0 rows affected (0.11 sec)
  6. mysql> insert into test_key_3 values(1,2,3),(4,5,6),(7,8,9);
  7. Query OK, 3 rows affected (0.02 sec)
  8. Records: 3 Duplicates: 0 Warnings: 0
  9. mysql> select *,_rowid from test_key_3;
  10. ERROR 1054 (42S22): Unknown column '_rowid' in 'field list' --这里无法用_rowid查看,因为系统rowid对用户是透明的

假设有 a 和 b 两张表都使用了系统定义的主键,则系统定义的主键的ID 不是表内 进行 单独递增 的,而是 全局递增

该系统的rowid是定义在 ibdata1.ibd 中的 sys_rowid 中,全局自增

6个字节表示的数据量为 2^48 ,通常意义上是够用的

注意:强烈建议自己显示定义主键


四. 页的结构

  1. File Header

在一个页中不仅仅只有记录,还有 File Header , Page Header , File Trailer


五. 记录

5.1 ROW_FORMAT

  1. REDUDENT 兼容老版本的InnoDBMySQL 4.1版本之前
  2. COMPACT MySQL 5.6 版本的默认格式
  3. COMPRESSED:支持压缩
  4. DYNAMIC:大对象记录优化, MySQL 5.7 版本默认格式

5.2 COMPACT 结构

  1. variable string length list
  2. - 变长字段 列表,表示有 多少个 变长字段,且序号 逆序 显示
  3. NULL flag
  4. - 是否有NULL
  5. rowid
  6. - B+树索引键值
  7. trx id
  8. - 事物ID6个字节
  9. roll pointer
  10. - 回滚指针,7个字节

5.3 COMPACT 示例

5.3.1 创建mytest表

  • 创建mytest表,格式为compact,且没有显示定义主键和非空唯一键,故使用系统定义的ROWID。并插入三条记录。
  1. create table mytest (
  2. t1 varchar(10),
  3. t2 varchar(10),
  4. t3 char(10),
  5. t4 varchar(10))
  6. engine=innodb row_format=compact;
  7. insert into mytest values (‘a’,‘bb’,‘bb’,‘ccc’);
  8. insert into mytest values (‘d’,‘ee’,‘ee’,‘fff’);
  9. insert into mytest values (‘d’,NULL,NULL,‘fff’);

5.3.2 将mytest表结构进行dump

图中红色部分对应第一条记录,黄色部分对应第二条记录,深蓝色部分对应第三条记录

  • 5.3.3 将红色部分对应的第一条记录进行解析

  • variable string length list

    • 03 02 01 ,表示有三个变长字段(varchar,varbinary,text等),且逆序存放(为了提高CPU的cache的命中率)
  • NULL flag
    • 00 , 这条记录中不存在NULL
  • Record Header
    • 5个字节,比较底层(比如看该记录有没有被删除)
  • RowID
    • 主键ID, 00 00 00 2b 68 00,从这个值可以看出,不是每张表从1开始递增的,是全局的ROWID
  • TransactionID
    • 事物ID
  • Roll Pointer
    • 回滚指针

5.3.3 char 和 varchar 的区别

多字节字符集 (如UTF8mb4)下:

  1. char(N) 中存储的数据的长度 范围 是 N ~ 4N ,当存储数据的长度 M , 未达到N 时,则填充空格(0x20),且空格的长度 取最小 的长度 N-M ,而 不是4N-M
  2. varchar(N) 则 不填充空格

    注意: char 数据类型本来是定长数据,但是在 多字节字符集 下,表现的行为和 varchar 类似,失去了原来的优势,当数据更新变长后可能无法 原地更新

5.3.4 原地更新(in place update)

  • 原地更新不会占用新的存储空间
  • 非原地更新需要删除(物理删除)原来的空间的数据,然后将更新后的数据插入到页的后面
  • 删除的数据的空间,会插入到Free_List链表的 头部
  • 原地更新 不会触发页的分裂

Free_List 是将页中被删除的空间串联在一起(组成一个 链表 ),当有数据被插到页内时,先看一下Free_list中 第一个空间 的大小,如果 空间合适 ,就将该记录 插入第一个空间中去,如果 不合适 ,直接插入到 页的尾部 的剩余空间。( 不会去看Free_list的第二个空间 )

当该页的数据被插满了,不会马上进行分页,而是进行 reorganize 操作,即将页内的数据在 内存 中进行整理,然后覆盖原来的页(不影响性能),所以InnoDB 不需要碎片整理

5.3.5 Reorganize

5.4 DYNAMIC

官方文档

  • DYNAMIC相比COMPACT,优化了大对象记录的存储。

    • 假设有一条记录有A,B,C,D 四列,其中D列的是text类型,且含有2W个字节的长度。
  • COMPACT

    • COMPACT会存储text中的前768个字节的数据,剩余的数据通过20个字节的指针指向溢出页

相对COMPACT,DYNAMIC在一个页中存储的 记录数 更多(因为有768字节的prefix,一条记录的字节假设是800字节,那16K的页只能存放20条记录,而之前我们测算可以存放80条记录),这样一来,B+树的高度可能会变高,读取的IO次数可能会变多。

一个页能存放的记录越多,则性能越优


六. heap number

  • heap_number表示页中每个 记录插入 的顺序 序号

    • 假设 插入 的数据是 a, b, d, e, g ;则对应的 heap_number 为 2,3,4,5,6

      -0 和 1 被 infimum 和 supermum 所使用

    1. - infimum 对应最小的heap_number
    2. - supermum 对应最大的heap_number,随着数据的插入,该值会更新
    • update对heap_number没有影响

    • heap_number是物理的,存储在row的 record_header 字段中

-- 终端1

  1. mysql> create table test_heap(a int primary key); Query OK, 0 rows affected (0.13 sec)
  2. mysql> insert into test_heap values (1); -- 插入a=1的记录
  3. Query OK, 1 row affected (0.03 sec)
  4. mysql> begin; -- 开启事物
  5. Query OK, 0 rows affected (0.00 sec)
  6. mysql> delete from test_heap where a=1; -- 删除a=1的记录,此时加上了锁
  7. Query OK, 1 row affected (0.00 sec)

-- 终端2

  1. mysql>mysql> show variables like "%innodb_status_output_locks%";
  2. +---------------------------- + -------+
  3. | Variable_name | Value |
  4. +---------------------------- + -------+
  5. | innodb_status_output_locks | OFF |
  6. +---------------------------- + -------+
  7. mysql> set global innodb_status_output_locks=1;
  8. Query OK, 0 rows affected (0.00 sec)
  9. mysql> pager less -- 使用类似linux中的less命令方式进行查看,可上下翻页
  10. PAGER set to 'less'
  11. mysql> show engine innodb status\G
  12. -- -----------省略其他输出-------------
  13. TABLE LOCK table `burn_test`.`test_heap` trx id 16943 lock mode IX
  14. RECORD LOCKS space id 122 page no 3 n bits 72 index PRIMARY of table `burn_test`.`test_heap` trx id 16943 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
  15. 0: len 4; hex 80000001; asc ;; -- 插入的主键a=18的二进制1000,最高位为1,表示有符号的
  16. 1: len 6; hex 00000000422f; asc B/;; -- 0x422f 十进制就是16943 ,表示事物idtrx id
  17. 2: len 7; hex 2c000000450dcf; asc , E ;; -- roll pointer(回滚指针)
  18. -- -----------省略其他输出-------------
  19. -- space id 122 : 表空间id122
  20. -- page no 3 : 对应的页号是3 (表示第4个页,是root页)
  21. -- heap no 2 : heap number2 (表示是新插入的第一条记录)
  22. -- heap no = 1 的一种情况

-- 终端1

  1. mysql> rollback;
  2. Query OK, 0 rows affected (0.00 sec)
  3. mysql> set tx_isolation='repeatable-read';
  4. Query OK, 0 rows affected (0.00 sec)
  5. mysql> select * from test_heap where a>1 for update;
  6. Empty set (0.00 sec)

-- 终端2

  1. mysql> show engine innodb status\G
  2. -- -----------省略其他输出-------------
  3. Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
  4. 0: len 8; hex 73757072656d756d; asc supremum;; -- 一条伪记录
  5. Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
  6. 0: len 4; hex 80000001; asc ;;
  7. 1: len 6; hex 00000000422e; asc B.;;
  8. 2: len 7; hex ab000000470110; asc G ;;
  9. -- -----------省略其他输出-------------

019:InnoDB 表空间内部组织结构的更多相关文章

  1. MySQL InnoDB表空间加密

    从 MySQL5.7.11开始,MySQL对InnoDB支持存储在单独表空间中的表的数据加密 .此功能为物理表空间数据文件提供静态加密.该加密是在引擎内部数据页级别的加密手段,在数据页写入文件系统时加 ...

  2. Innodb 表空间传输迁移数据

    在mysql5.5之前,mysql实例中innodb引擎表的迁移是个头疼的问题,要么使用mysqldump导出,要么使用物理备份的方法,但是在mysql5.6之后的版本中,可以使用一个新特性,方便地迁 ...

  3. MySQL Innodb表空间不足的处理方法

    官方给出的解决方案: 添加和删除 InnoDB 数据和日志文件 这一节描述在InnoDB表空间耗尽空间之时,或者你想要改变日志文件大小之时,你可以做的一些事情. 最简单的,增加InnoDB表空间大小的 ...

  4. 10. InnoDB表空间加密

    10. InnoDB表空间加密 InnoDB支持存储在单独表空间中的表的数据加密 .此功能为物理表空间数据文件提供静态加密. 详细信息见官方文档

  5. ibd2sdi — InnoDB表空间SDI提取实用程序

    参考mysql8.0官方文档 https://dev.mysql.com/doc/refman/8.0/en/ibd2sdi.html ibd2sdi是一个实用程序,用于从表空间文件中提取 序列化的字 ...

  6. Innodb 表空间卸载、迁移、装载

    从MySQL的Innodb特性中我们知道,Inndob的表空间有共享和独享的特点,如果是共享的.则默认会把表空间存放在一个文件中(ibdata1),当开启独享表空间参数Innodb_file_per_ ...

  7. InnoDB表空间、段、区

    1. 表空间是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中.默认,InnoDB存储引擎只有一个表空间ibdata1,即所有数据都存放在这个表空间内.如果用户启用了参数innodb_ ...

  8. Innodb表空间

    Innodb有两种管理表空间的方法 独立表空间:每一张表都会生成独立的文件来进行存储,每一张表都有一个.frm表描述文件,和一个.ibd文件.其中ibd文件包括了单独一个表的数据内容和索引内容. 共享 ...

  9. Innodb表空间迁移过程

    1.大致流程 将a实例的表的数据迁移到b实例上. 1.在目标实例b上创建一个相同的表 2.在目标库b上执行ALTER TABLE t DISCARD TABLESPACE; 3.在源库a上执行FLUS ...

随机推荐

  1. easyui(1)

    使用Easyui1.引入必要的文件 1).jquery核心库 2).easyui核心库 3).easyui中文提示信息 4).自己开发的js文件 5).easyui核心UI文件css 6).easyu ...

  2. eclipse ndk 配置和简单开发demo

    记录下以备忘: android开发的各种资源国内镜像 http://www.androiddevtools.cn/ 前端时间尝鲜用android stuido1.5开发了个android的小项目,发现 ...

  3. 剑指offer--43.连续子数组的最大和

    最大子段和,最大能取所有 ---------------------------------------------------------------- 时间限制:1秒 空间限制:32768K 热度 ...

  4. Datesheet 参数手册

    1 明白P/N每一个字是代表什么含义... 这很重要! EP6095-01-381 (CL10C010CBNNNC=CL10C010CB8NNNC ?)----8 代表Ni plating in te ...

  5. WIP - Study Perf (by quqi99)

    版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 (http://blog.csdn.net/quqi99) Perf Flame Graph sudo perf ...

  6. Elasticsearch安装配置和测试

    官方教程:https://www.elastic.co/guide/en/elasticsearch/reference/master/_installation.html 中文教程:https:// ...

  7. ibatis 参数类型为map,map里面有list

    <select id="getChannelLayerList" parameterClass="java.util.HashMap"        re ...

  8. python文件写中的f.flush()方法

    f = open("input.txt", "w") f.write("aaaaaa") f.flush() #强行把缓冲区中的内容放到磁盘 ...

  9. JAVA多线程----用--进阶--》网络编程2

    import java.io.*; import java.net.*; /** * 服务器端逻辑线程 */ public class LogicThread extends Thread { Soc ...

  10. 微信网页登录Tips

    http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html 以这篇文章为例,一般都是用户在第三方app中登录时,由第三方去申请资源服务器的登录权限等.即 ...