在MySQL中InnoDB属于存储引擎层,并以插件的形式集成在数据库中。从MySQL5.5.8开始,InnoDB成为其默认的存储引擎。InnoDB存储引擎支持事务、其设计目标主要是面向OLTP的应用,主要特色有:支持事务、行锁设计支持高并发、外键支持、自动崩溃恢复、聚簇索引的方式组织表结构等。mysql
InnoDB存储引擎是由内存池、后台线程、磁盘存储三大部分组成。sql
InnoDB 使用的是多线程模型, 其后台有多个不一样的线程负责处理不一样的任务数据库
Master Thread是最核心的一个后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性。包括脏页刷新、合并插入缓冲、UNDO页的回收等。缓存
在 InnoDB 存储引擎中大量使用了异步IO(Async IO)来处理写IO请求, IO Thread的工做主要是负责这些 IO 请求的回调。markdown
事务提交后,其所使用的undo log可能再也不须要,所以须要Purge Thread来回收已经分配并使用的UNDO页。InnoDB支持多个Purge Thread, 这样作能够加快UNDO页的回收,提升CPU的使用率以及提高存储引擎的性能。数据结构
Page Cleaner Thread的做用是取代Master Thread中脏页刷新的操做,其目的是减轻原Master Thread的工做及对于用户查询线程的阻塞,进一步提升InnoDB存储引擎的性能。多线程
InnoDB 存储引擎内存的结构架构
InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。可是因为CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统一般使用缓冲池记录来提升数据库的的总体性能。并发
缓冲池其实就是经过内存的速度来弥补磁盘速度较慢对数据库性能的影响。在数据库进行读取操做时,首先将磁盘中的页放入缓冲池中,下次再读取相同页时,首先从缓冲池中获取该页数据,起到高速缓存的做用。异步
数据的修改操做,则首先修改在缓冲池中的页数据,而后使用一种称为Checkpoint的机制刷新到磁盘上。
缓冲池的大小直接影响数据库的总体性能,对于InnoDB存储引擎而言,缓冲池配置经过参数 innodb_buffer_pool_size 来设置。使用 SHOW VARIABLES LIKE 'innodb_buffer_pool_size'
命令可查看缓冲池配置:
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size' \G *************************** 1. row *************************** Variable_name: innodb_buffer_pool_size Value: 134217728 1 row in set (0.01 sec)
缓冲池中缓存的数据页类型有: 索引页、undo页、插入缓冲、自适应哈希索引、InnoDB锁信息、数据字典信息等,索引页和数据页占缓冲池很大的一部分。
缓冲池中的页数据比磁盘要新时,须要将新数据刷新到磁盘中。InnoDB采用Write Ahead Log策略来刷新数据,即当事务提交时,先写入重作日志缓冲,重作日志缓冲会按必定频率刷新到重置日志文件中,而后脏页会根据checkpoint机制刷新到磁盘。
重作日志缓冲不须要设置很大,一般状况下8M能知足大部分的应用场景。重作日志支持一下三种状况触发刷新:
在InnoDB存储引擎中,对内存的管理是经过一种称为内存堆的方式进行的。在对一些数据结构自己的内存进行分配时,须要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请。
InnoDB支持的锁有:
InnoDB引擎实现了两种标准的行级锁,共享锁(shared (S) locks) 和排他锁 (exclusive (X) locks)。共享锁容许一个占有锁的事务去读取一行数据,排它锁则容许事务对某一行记录进行写操做。
若是一个事务持有了一个共享锁,其余事务仍然能够获取这行记录的共享锁,但不能获取到这行记录的排它锁。当一个事务获取到了某一行的排它锁,则其余事务将没法再获取这行记录的共享锁和排它锁。
在InnoDB中,意向锁是一种表级锁,分为共享锁和排他锁:
事务在获取共享/排它锁以前必须先获取意向共享/排它锁,意向锁不会阻塞其余任何对表的操做,他只是告诉其余事务他将要去获取某一行的共享锁或者排他锁。
记录是是做用在索引上的一种锁,他锁住的是某一条记录的索引而非记录自己,若是当前表没有索引那么InnoDB将会为其建立一个隐藏的汇集索引,而Record Locks将会锁住这个隐藏的汇集索引。
间隙锁和记录锁同样也是做用在索引上,不一样的是记录锁只做用于一条索引记录而间隙锁能够锁住一个范围内的索引。间隙锁在InnoDB的惟一做用就是防止其余事务的插入操做,以此防止幻读的发生。
自增锁是一种特殊的表级锁,他只做用在包含自增列的插入操做时。当一个事务正在插入一条数据时,其余的任何事务都必须等待整个事务完成插入操做,在取获取锁来执行插入操做。
事务是数据库做为OLTP最为重要的特性,提及事务不得不提起ACID四个基本特性:
InnoDB的原子性、持久性和一致性主要是经过Redo Log、Undo Log和Force Log at Commit机制机制来完成的。Redo Log用于在崩溃时恢复数据,Undo Log用于对事务的影响进行撤销,也能够用于多版本控制。而Force Log at Commit机制保证事务提交后Redo Log日志都已经持久化。隔离性则是由锁和MVCC来保证的。
在MySQL中,事务有4种隔离级别,分别是:
在理解四种隔离级别以前,咱们须要先了解另外三个名词:
a事务会读取到b事务还未提交的数据,可是b事务因为某种缘由进行回滚操做,这样,a事务读取的数据是不可用的,进而会形成一些异常结果。
a事务周期内对某一数据屡次查询,同时这些数据在b事务中进行了update或delete操做。那么a事务每次查询出来的结果可能都不同。
幻读的结果其实和不可重复读是同样的表现,差别就在于不可重复读主要是针对其余事务进行了编辑(update)和删除(delete)操做。而幻读主要是针对插入(insert)操做。也就是在一个事务生命周期内,会查询到另一个事务新插入的数据。
未提交读,这种状况下,一个事务a能够看到另外一个事务b未提交的数据,若是此时事务b发生回滚,那么事务a拿到的就是脏数据,这也就是脏读的含义。
此隔离级别在MySQL InnoDB通常不推荐使用。
已提交读,一个事务从开始直到提交以前,所作的任何修改对其余事务都是不可见的。解决了脏读问题,可是存在幻读现象。
可重复读,该级别保证在同一事务中屡次读取一样记录的结果是一致的,在InnoDB存储引擎中同时解决了幻读和不可重复读问题。
InnoDB引擎经过使用Next-Key Lock
解决了幻读的问题。Next-Key Lock
是行锁和间隙锁的组合,当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁以后,其余事务就不能在这个间隙修改或者插入记录。
Serializable 是最高的隔离级别,它经过强制事务串行执行,避免了幻读的问题,可是 Serializable 会在读取的每一行数据上都加锁,因此可能致使大量的超时和锁争用的问题,所以并发度急剧降低,在MySQL InnoDB一样不被建议使用。
执行BEGIN命令不会真正在引擎层开启新事务,仅仅是为当前线程设定标记,表示为显式开启的事务。
开启只读事务,当MySQL Server接收到任何数据更改的SQL时,都会直接拒绝修改并返回错误,此错我不会进入引擎层。
容许super用户在当前线程只读状态为true的状况下启动读写事务。
开启事务会进入引擎层,并开启一个readview
。只有在RR隔离级别下,这种操做才有效,不然会报错。
在数据进行修改时会记录相应的undo日志,若是事务失败或者回滚,能够借助记录的undo日志进行回滚。Undo log是逻辑日志,记录更改前的数据镜像。在修改时若是同时须要读取当前数据的时候,它能够根据版本信息分析出该行记录之前版本的数据。另外Undo log也会产生重作日志,由于Undo log也要进行持久化保护。
trx_t
)添加到全局提交事务链表(trx_serial_list
)中TRX_UNDO_CACHED
,若是不知足且是insert undo
则标记为TRX_UNDO_TO_FREE
,不然undo为update undo则标记为TRX_UNDO_TO_PURGE
。标记为TRX_UNDO_CACHED
的undo会被引擎回收。update undo
放入所在undo segment
的history list
,并递增rseg_history_len
(全局)。同时更新Page上的TRX_UNDO_TRX_NO
, 若是删除了数据,则重置delete_mark
undate undo
从update_undo_list
中删除,若是被标记为TRX_UNDO_CACHED
,则加入到update_undo_cached
队列中mtr_commit
(日志undo/redo写入公共缓冲区),至此,在文件层次事务提交。这个时候即便crash,重启后依然能保证事务是被提交的。接下来要作的是内存数据状态的更新(trx_commit_in_memory
)readview
从全局readview
链表中移除,而后重置trx_t
结构体里面的信息便可。读写事务首先须要是设置事务状态为TRX_STATE_COMMITTED_IN_MEMORY
,释放全部行锁而且将trx_t
从rw_trx_list
中移除,readview
从全局readview
链表中移除。若是有insert undo
则在这里移除,若是有update undo
则唤醒Purge线程进行垃圾清理,最后重置trx_t
里的信息,便于下一个事务使用update undo
和insert undo
中找出最后一条undo,从这条undo开始回滚update undo
则将标记为删除的记录清理标记,更新过的数据回滚到最老的版本。若是是insert undo
则直接删除汇集索引和二级索引InnoDB引擎使用B+树做为索引结构,主键索引的叶子节点data域保存了完整的字段数据,非主键索引的叶子节点保存了指向主键的值数据。
上图是 InnoDB 主索引(同时也是数据文件)的示意图,能够看到叶节点包含了完整的数据记录,这种索引叫作汇集索引。由于 InnoDB 的数据文件自己要按主键汇集,因此 InnoDB 要求表必须有主键,若是没有显式指定,则 MySQL 系统会自动选择一个能够惟一标识数据记录的列做为主键,若是不存在这种列,则 MySQL 自动为 InnoDB 表生成一个隐含字段做为主键,这个字段长度为6个字节,类型为长整形。
InnoDB 的辅助索引 data 域存储相应记录主键的值而不是地址。换句话说,InnoDB 的全部辅助索引都引用主键做为 data 域。汇集索引这种实现方式使得按主键的搜索十分高效,可是辅助索引搜索须要检索两遍索引:首先检索辅助索引得到主键,而后用主键到主索引中检索得到记录。
对于MySQL InnoDB的诸多特性,本文只介绍了很小的一部分,感兴趣的同窗可阅读 《MySQL技术内幕:InnoDB存储引擎》了解更多相关知识。