表空间(ibd文件),一个 mysql 实例可以对应多个表空间,用于存储记录、索引等数据。
查看 Linux 系统中的表空间:
[root@192 itcast]# cd /var/lib/mysql
[root@192 mysql]# cd itcast
[root@192 itcast]# ls -al
总用量 932
drwxr-x---. 2 mysql mysql 149 8月 6 18:03 .
drwxr-xr-x. 10 mysql mysql 4096 8月 4 17:04 ..
-rw-r-----. 1 mysql mysql 131072 8月 5 16:17 course.ibd
-rw-r-----. 1 mysql mysql 114688 8月 6 16:26 score.ibd
-rw-r-----. 1 mysql mysql 147456 8月 4 11:58 student_course.ibd
-rw-r-----. 1 mysql mysql 147456 8月 6 18:03 student.ibd
-rw-r-----. 1 mysql mysql 114688 8月 6 10:42 tb_user_ext.ibd
-rw-r-----. 1 mysql mysql 180224 8月 6 12:01 tb_user.ibd
-rw-r-----. 1 mysql mysql 114688 8月 6 12:01 user_logs.ibd
段(Segment),分为数据段(Leaf node segment)、索引段(Non-leaf node segment)、回滚段(Rollback segment)。段用来管理多个区(Extent)。
区(Extent),每个区的大小为 1M。默认情况下,页(Page)大小为 16k,因此一个区包含 64 个连续的页。
页(Page),InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 每次从磁盘申请 4~5 个区。
行(Row),InnoDB 引擎的数据是按照行进行存放的。行中包含的信息有:
-
Trx id,最后一次访问的数据行 id。
-
Roll pointer,指向修改前的旧数据的指针。
架构
分为两个部分:
-
In-Memory Structure:内存结构
-
On-Disk Structure:磁盘结构
内存结构
Buffer Pool
Buffer Pool(缓冲池):主内存中的一个区域,用于缓存磁盘中频繁读写的数据。在执行增删改查操作时,会先操作缓冲池中的数据(如果没有,从磁盘加载到缓冲池),然后再以一定频率刷新到磁盘,以减少磁盘 IO 提高性能。
缓冲池以页(Page)为单位存储数据,页与页之间通过链表结构进行组织。Page 分为三种类型:
-
free page:空闲页,指未被使用的页。
-
clean page:被使用,但数据与磁盘一致的页。
-
dirty page:脏页,被使用,数据与磁盘不一致的页。
Change Buffer
Change Buffer(更改缓冲区):针对非唯一二级索引,在执行 DML 语句时,如果这些数据 Page 没有在缓冲池中,不会直接操作磁盘,而是将数据变更操作写入更改缓冲区中,在未来数据被读取时,再将数据合并到缓冲池,并在之后刷新到磁盘。
Adaptive Hash Index
自适应哈希索引,用于优化缓冲池数据查询。InnoDB 会监控对表上索引页的查询,如果观察到哈希索引可以提升速度,就会建立哈希索引,这个索引称之为自适应哈希索引。
是否开启的开关可以通过下面的查询查看:
mysql> show variables like '%hash%';
+----------------------------------+-------+
| Variable_name | Value |
+----------------------------------+-------+
| innodb_adaptive_hash_index | ON |
| innodb_adaptive_hash_index_parts | 8 |
+----------------------------------+-------+
innodb_adaptive_hash_index = ON
表示已经开启自适应哈希索引。
Log Buffer
日志缓冲区,用于保存要写入磁盘的日志数据(redo log 和 undo log),默认大小 16 MB,日志缓冲区会定期刷新到磁盘。可以通过增加日志缓冲区大小以节省磁盘 IO 提升性能。
查看日志缓冲区大小:
mysql> show variables like '%log_buffer_size%';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
查看日志写入的规则:
mysql> show variables like '%flush_log%';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_timeout | 1 |
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
innodb_flush_log_at_trx_commit
有三个可选项:
-
1:每次事务提交都会写入日志到缓冲区,并刷新到磁盘。
-
0:每秒将日志写入缓冲区,并刷新到磁盘
-
2:每次事务提交后将日志写入缓冲区,每秒刷新到磁盘。
磁盘结构
System Tablespace
系统表空间,保存更改缓冲区(Change Buffer)。
查看系统表空间文件:
mysql> show variables like '%file_path%';
+----------------------------+------------------------+
| Variable_name | Value |
+----------------------------+------------------------+
| innodb_data_file_path | ibdata1:12M:autoextend |
| innodb_temp_data_file_path | ibtmp1:12M:autoextend |
+----------------------------+------------------------+
innodb_data_file_path
的值ibdata1
就是系统表空间的文件名,即:
cd /var/lib/mysql
ls -al ibdata1
-rw-r-----. 1 mysql mysql 12582912 8月 6 18:03 ibdata1
File-Per-Table Tablespaces
每张表的独立表空间。
由参数innodb_file_per_table
决定是否开启:
mysql> show variables like '%file_per_table%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_file_per_table | ON |
+-----------------------+-------+
开启后每张表都拥有独立的表空间文件(ibd):
sudo ls -al ./itcast
drwxr-x---. 2 mysql mysql 149 8月 6 18:03 .
drwxr-xr-x. 10 mysql mysql 4096 8月 4 17:04 ..
-rw-r-----. 1 mysql mysql 131072 8月 5 16:17 course.ibd
-rw-r-----. 1 mysql mysql 114688 8月 6 16:26 score.ibd
-rw-r-----. 1 mysql mysql 147456 8月 4 11:58 student_course.ibd
-rw-r-----. 1 mysql mysql 147456 8月 6 18:03 student.ibd
-rw-r-----. 1 mysql mysql 114688 8月 6 10:42 tb_user_ext.ibd
-rw-r-----. 1 mysql mysql 180224 8月 6 12:01 tb_user.ibd
-rw-r-----. 1 mysql mysql 114688 8月 6 12:01 user_logs.ibd
General Tablespace
通用表空间,默认不存在,需要使用命令显式创建:
mysql> create tablespace ts_itheima add datafile 'ts_itheima.ibd' engine = innodb;
Query OK, 0 rows affected (0.07 sec)
创建好后就能在 MySQL 的目录找到对应的表空间文件:
ls -al ts_itheima.ibd
-rw-r-----. 1 mysql mysql 114688 8月 7 10:47 ts_itheima.ibd
创建表时可以将表关联到这个通用表空间,而不是新建表自己的表空间:
mysql> create table employee(id int unsigned primary key, name varchar(10)) engine=innodb tablespace ts_itheima;
Query OK, 0 rows affected (0.07 sec)
Temporary Tablespace
临时表空间,包含会话临时表空间和全局临时表空间,用于存储用户创建的临时表等数据。
Doublewrite Buffer File
双写缓冲区,innoDB 引擎将数据页从缓冲池刷新到磁盘前,会先将数据页写入双写缓冲区文件,以便在出现数据冲突时恢复数据。
双写缓冲区文件:
ls -al | grep .dblwr
-rw-r-----. 1 mysql mysql 196608 8月 7 10:52 #ib_16384_0.dblwr
-rw-r-----. 1 mysql mysql 8585216 8月 6 18:03 #ib_16384_1.dblwr
Redo Log
重做日志,用于实现事务的持久性。由两部分组成:重做日志缓冲(redo log buffer)和重做日志文件(redo log file),前者在内存中(log buffer),后者在磁盘中。当事务提交时,会将数据修改信息写入重做日志,如果将脏页数据刷新到磁盘时发生数据冲突,会利用重做日志进行数据恢复。
后台线程
Master Thread
核心后台线程,负责调度其它线程,还负责将缓冲池中的数据刷新到磁盘,以保持数据的一致性。还包括脏页的刷新、合并插入缓存、undo 页的回收。
IO Thread
InnoDB 使用 AIO(异步 IO) 处理 IO 请求, IO Thread 主要负责这些 IO 请求的回调。
线程类型 | 默认个数 | 职责 |
---|---|---|
Read Thread | 4 | 负责读操作 |
Write Thread | 4 | 负责写操作 |
Log Thread | 1 | 负责将日志缓冲区刷新到磁盘 |
Insert Buffer Thread | 1 | 负责将写缓冲区内容刷新到磁盘 |
通过以下命令可以查看 innoDB 引擎的状态:
show engine innodb status;
其中的 IO Tread 信息:
FILE I/O -------- I/O thread 0 state: waiting for completed aio requests (insert buffer thread) I/O thread 1 state: waiting for completed aio requests (read thread) I/O thread 2 state: waiting for completed aio requests (read thread) I/O thread 3 state: waiting for completed aio requests (read thread) I/O thread 4 state: waiting for completed aio requests (read thread) I/O thread 5 state: waiting for completed aio requests (write thread) I/O thread 6 state: waiting for completed aio requests (write thread) I/O thread 7 state: waiting for completed aio requests (write thread) I/O thread 8 state: waiting for completed aio requests (write thread) Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] , ibuf aio reads:
可以看到有 1 个 insert buffer thread,4个 read thread,4个 write thread。此外reads: [0, 0, 0, 0]
表示4个 read thread 都处于空闲状态,writes: [0, 0, 0, 0]
表示4个 write thread 也处于空闲状态。
Purge Thread
主要用于回收事务已经提交的 undo log,在事务成功提交之后,undo log 失去作用,由 Purge Thread 进行回收。
Page Cleaner Thread
协助 Master Thread 刷新脏页到磁盘,可以减轻 Master Thread 的工作压力,减少阻塞。
事务原理
redo log
重做日志,记录的是事务提交时数据页的物理修改,用来实现事务的持久性。
事务提交后,会修改内存缓冲池中的数据,但不会立即将数据刷新到磁盘中的表空间文件,而是在一定时间后统一将脏页数据刷新到磁盘。如果这段时间内系统出现问题,或者刷新脏页时出错,就会导致事务进行的修改失效(违反事务的持久性原则)。为了避免这种情况出现,会在事务提交并修改内存中的缓冲池后,将事务引起的数据变更写入内存的重做日志缓冲(redo log buffer),并立即刷新到磁盘的重做日志文件(redo log file)。如果系统发生故障,就可以利用日志重做文件对事务进行恢复,将事务引起的数据变化写入磁盘的表空间文件,以确保事务的持久性。
上述内容的过程演示可以观看这个。
undo log
回滚日志,用于记录数据被修改前的信息,作用包含:提供回滚和MVCC(多版本并发控制)。
undo log 与 redo log 不同,前者记录的是逻辑日志,后者记录的是物理日志。即如果事务执行 delete 时,undo log 会记录一条相反的 insert 语句。
undo log 可以解决事务的原子性,当事务出错,执行 rollback 时,可以通过 undo log 中的反向 SQL 执行恢复操作。
MVCC
基本概念
当前读
读取到的是记录的最新版本,还要保证其他并发事务不能修改当前记录,会对读取记录进行加锁。
会产生当前读的 SQL:
-
select ... lock in share mode
-
select ... for update
-
update
-
insert
-
delete
示例,在客户端1中开启一个事务,并查询一条数据:
mysql> begin;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from student where id=1;
+----+------+------------+-----+
| id | name | no | age |
+----+------+------------+-----+
| 1 | php | 2000100101 | 3 |
+----+------+------------+-----+
在客户端2中修改这条数据:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update student set name='joke' where id=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
在客户端1的事务中再次查询:
mysql> select * from student where id=1;
+----+------+------------+-----+
| id | name | no | age |
+----+------+------------+-----+
| 1 | php | 2000100101 | 3 |
+----+------+------------+-----+
会发现看不到数据改变,这是因为当前 MySQL 使用的事务隔离级别是 repeatable read,所以默认的 SELECT 语句获取到的并不是最新数据,不是当前读。
如果要当前读:
mysql> select * from student where id=1 lock in share mode;
+----+------+------------+-----+
| id | name | no | age |
+----+------+------------+-----+
| 1 | joke | 2000100101 | 3 |
+----+------+------------+-----+
这样做会返回最新数据,并且给数据行添加共享锁。
快照读
简单的 SELECT(不加锁)就是快照读,读取的是记录的快照版本,有可能是历史数据,是非阻塞的。
不同的隔离级别有不同的行为:
-
Read Committed:每次 SELECT 都生成一个快照读。
-
Repeatable Read:开启事务后第一次执行 SELECT 语句是快照读。
-
Serializable:快照读退化为当前读。
MVCC
全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为 MySQL 实现 MVCC 提供了一个非阻塞读功能。MVCC 的具体实现,依赖于数据库记录中的三个隐藏字段、undo log 日志、readView。
实现原理
隐藏字段
创建表时,除了用于指定的字段,MySQL 会添加3个隐藏字段:
-
DB_TRX_ID,记录最后一次修改记录的事务ID
-
DB_ROLL_PTR,回滚指针,配合 undo log,可以回溯记录修改前的内容
-
DB_ROW_ID,隐藏主键,如果表结构没有指定主键,又没有唯一索引,创建该字段作为隐藏主键
通过表空间文件可以查看隐藏字段:
ibd2sdi student.ibd| more
undo log
当 insert 的时候,产生的 undo log 日志只在回滚时需要,在事务提交后可以被立即删除。
事务执行 update、delete 的时候,产生的 undo log 日志不仅在回滚时需要,在其它事务进行快照读的时候也需要,不会被立即删除。
不同的事务或相同的事务对同一条数据进行修改,会导致该记录的 undo log生成一条记录版本链表,链表的头部是最新的旧记录,链表的尾部是最早的旧记录。
readview
事务产生读快照时会生成一个 ReadView:
字段 | 含义 |
---|---|
m_ids | 当前活跃的事务 ID 集合 |
min_trx_id | 最小活跃事务 ID |
max_trx_id | 预分配事务 ID,当前最大事务 ID+1 |
creator_trx_id | ReadView 创建者的事务 ID |
结合这个 ReadView 以及 undo log 版本链就可以确定读快照中的数据。
在 RC 事务隔离级别下,每次事务执行 SELECT 都将产生一个 ReadView,其快照匹配数据的过程可以观看这个。
大致意思是让快照匹配到事务自身修改的数据或者最近一个已提交的事务修改后的数据。
在 RR 事务隔离级别下,仅在事务中第一次执行快照读时生成 ReadView,后续复用该 ReadView。
文章评论