InnoDB架构
InnoDB
内存架构
BufferPool
主内存的一块区域,InnoDB在表和索引数据被访问的时候缓存它。在专用服务器上,高达80%的物理内存通常分配给bufferpool。配置项:
innodb_buffer_pool_size
,正常推荐设置50%-75%的系统内存
实现为page链表,缓存里很少的数据会使用LRU算法过时掉
实现
ChangeBuffer当二级索引page不在bufferpool时,缓存对这些二级索引page的更改。对于buffer的更改,稍后当page被其他读操作加载进bufferpool的时候,会进行合并
ChangeBuffer
在内存中,changebuffer占用部分的bufferpool。在磁盘上,changebuffer是系统表空间的一部分,当数据库服务器关闭的时候,索引的更改将在其中进行缓存
AdaptiveHashIndex自适应Hash索引,使InnoDB在具有适当的工作负载和有足够内存的bufferpool组合的系统上,执行起来更像内存中的数据库,而不会牺牲事务特性和可靠性。根据观察到的搜索模式,使用索引key的前缀建立hash索引。hash索引是根据需要为经常需要访问的索引page构建的。如果一个表差不多可以整个放进主内存,那么hash索引通过启用任何元素的直接查找来加快查询,将索引值转换为一种指针。
LogBuffer持有将要被写入磁盘上的日志文件的内存区域。logbuffer的内容会定期刷写到磁盘。默认16MB。大的logbuffer能让大的事务运行而不需要在事务提交前将redolog数据写到磁盘上。所以如果有事务更新、插入或者删除很多行,增大logbuffer能减少磁盘IO
磁盘结构
表Tables
索引Indexes聚簇索引能加快查询,因为索引搜索能直接指向包含行数据的page。如果表很大,聚簇索引通常能够节省磁盘IO操作聚簇索引之外的其他所有叫做二级索引。在innodb里,二级索引的每个记录都包含了行的主键列,以及为二级索引指定的列如果主键key很长,二级索引也会使用更多的空间InnoDB索引是B-Tree结构,空间索引是R-Tree结构。索引记录存储在B-Tree或R-Tree的叶子page上
表空间Tablespaces
system表空间存储changebuffer的区域。如果表创建在system表空间而不是file-per-table或general表空间,它也可能存储表和索引数据。它可以有一个或多个数据文件,默认是单个系统表空间数据文件,名称为ibdata1
File-Per-Table表空间包含单个InnoDB表的数据和索引,存储在文件系统的单个数据文件里文件名为:table_name.ibd
General表空间通过使用CREATETABLESPACE语法创建的共享InnoDB表空间和system表空间类似,能够存储多个表的数据的共享表空间相对于file-per-table表空间,通用表空间有潜在的内存优势。服务器会在表空间的生命周期内将表空间的元数据保留在内存内,多个表在数量更少的通用表空间的占用表空间元数据的内存会更少
Undo表空间包含undo日志,它是记录的集合,包含了怎么撤销事务对聚簇索引最新的修改的信息Undo日志默认存储在system表空间,但也可以存储在一个或多个undo表空间里
Temporary表空间包含Session临时表空间,一个全局临时表空间session临时表空间存储着用户创建的临时表,以及当innodb配置为磁盘内部临时表存储引擎时优化器创建的内部临时表(从MYSQL8.0.16起磁盘内部临时表使用的存储引擎永远是InnoDB)全局临时表空间(ibtmp1)存储用户创建的临时表所做更改的回滚块
DoublewriteBuffer是个存储区域,InnoDB在将page写入InnoDB数据文件中的适当位置之前,会在其中写入从bufferpool中刷新的page。如果在page写的过程中有操作系统、存储子系统,或者mysqld的异常退出,InnoDB可以在崩溃恢复期间从doublebuffer找到一个好的副本
RedoLog是一种基于磁盘的数据结构,用于在崩溃恢复期间用于更正由于不完整的事务写入的数据。在异常停机前没有完成更新数据文件的修改,在初始化期间和接收连接之前会自动重放默认在磁盘上有ib_logfile0和ib_logfile1。MYSQL以循环方式写入redolog
UndoLogs一个undolog是与单个读写事务关联的undolog记录集合。一个undolog记录包含了如何撤销一个事务对一条聚簇索引记录最近修改的信息。如果其他事务需要将原始事务视为一致性读的一部分,则会从undolog记录中检索出未修改的数据。undologs存在于undolog段里,undolog段包含于回滚段里,回滚段驻留于system表空间、undo表空间和全局临时表空间驻留于全局临时表空间中的undologs用于在用户定义的临时表里修改数据的事务。这些undologs没有被redologged,因为崩溃恢复不需要它们。它们只用于在服务器运行期间回滚。这种类型的undologs通过避免redologging的IO而提升了性能
一个事务最多分配4个undologs,每个都是下面的操作类型之一:
INSERT操作,用户定义的表
UPDATE和DELETE操作,用户定义的表
INSERT操作,用户定义的临时表
UPDATE和DELETE操作,用户定义的临时表
InnoDB锁和事务模型
InnoDB锁
共享锁和排他锁
共享锁shared(s)锁允许持有锁的事务读取一行
排他锁exclusive(x)锁运行持有锁的事务更新或者删除行
意向锁IntentionLocks
表级锁,用于指示事务稍后在表里需要哪种类型的锁(共享或排他)
intentionsharedlock(IS)表明事务将在表里独立的行上设置共享锁
intentionexclusivelock(IX)表明事务将在表里独立的行上设置排他锁
SELECT...FORSHARE设置IS锁,SELECT...FORUPDATE设置IX锁
事务在能获取表里行的共享锁之前,必须先获取表的IS锁或更强的锁事务在能获取表里行的排他锁之前,必须先获取表的IX锁或更强的锁
记录锁RecordLocks
在索引记录上的锁记录锁始终锁定索引记录,即使表上没有定义索引。这种情况下,InnoDB会创建一个隐式的聚簇索引,使用这个索引锁定记录
间隙锁GapLocks
索引记录间隙之间的锁,或者第一个索引记录之前或最后一个索引记录之后的间隙的锁e.g.SELECTc1FROMtWHEREc1BETWEEN10and20FORUPDATE;会阻止其他事务插入15的值到t.c1列,而不用管在这个列中有没有这个值
对于使用唯一索引搜索唯一行的语句,不需要间隙锁。e.g.列id有唯一索引,下面这语句只对id=的行使用索引记录锁,其他会话是否在前面的间隙中插入行并不重要
SELECT*FROMchildWHEREid=;
如果id没有被索引,或者没有唯一索引,则语句会锁定前面的间隙
InnoDB的间隙锁是“纯抑制性的”,它的唯一目的是阻止其他事务插入到这个间隙,间隙锁可以共存,共享和排他间隙锁没有差别
间隙锁是可以被禁用的,如果被禁用可能会导致幻读问题,因为其他session可以将新行插入间隙
Next-Key锁
是索引记录上的记录锁和索引记录之前间隙上的间隙锁的组合如果一个session在索引的记录R上有一个共享或排他锁,则另一个session不能在索引顺序中紧靠R之前的间隙中立即插入新的索引记录假设索引包含值10,11,13,20。那么这个索引上可能的next-key锁包含下面的几种间隔
(negativeinfinity,10](10,11](11,13](13,20](20,positiveinfinity)
默认InnoDB运行在REPEATABLEREAD事务隔离级别。这种情况下,InnoDB使用next-key锁来进行搜索和索引扫描
插入意向锁InsertIntentionLocks
行插入之前通过INSERT操作设置的一种间隙锁。此锁表示插入的意图,如果插入到同一个索引间隙的多个事务不在间隙的同个位置插入,则它们无需互相等待假设有索引记录值4和7。不同的事务尝试插入5和6,在获得插入行的排它锁之前,每个事务都会使用插入意向锁锁住4和7之间的间隙,但因为行不冲突而不会互相阻塞e.g.客户端A创建一个表,包含了两个索引记录90和,然后启动一个事务,对ID大于的索引记录进行排它锁。这个排它锁包含了记录前面的间隙
mysqlCREATETABLEchild(idint(11)NOTNULL,PRIMARYKEY(id))ENGINE=InnoDB;mysqlINSERTINTOchild(id)values(90),();mysqlSTARTTRANSACTION;mysqlSELECT*FROMchildWHEREidFORUPDATE;+-----+
id
+-----+
+-----+
客户端B开始一个插入记录到间隙的事务。这个事务在它等待获取排它锁时接受插入意向锁
mysqlSTARTTRANSACTION;mysqlINSERTINTOchild(id)VALUES();
AUTO-INC锁
是一种特殊的表锁,事务插入有AUTO_INCREMENT列的表的时候使用。如果一个事务正在向表插入值,任何其他事务在插入这个表的时候都必须等待,以便第一个事务接收连续的主键值
InnoDB事务模型
事务隔离级别
REPEATABLEREAD(默认级别)相同事务里的一致性读,使用第一次读的时候建立的快照。意味着在同一个事务里,如果发出了几个普通(非阻塞)的SELECT语句,这些SELECT语句彼此之间也是一致的对于锁定读(SELECT带有FORUPDATE或FORSHARE),UPDATE,DELETE语句,锁定依赖于语句是否在唯一搜索条件里使用了唯一索引,或者范围搜索条件
对于唯一搜索条件的唯一索引,InnoDB只锁定找到的索引记录,不包含它前面的间隙
对于其他搜索条件,InnoDB锁定索引扫描的范围,使用间隙锁或next-key锁阻止其他session插入间隙所覆盖的范围。
READCOMMITTED即使在同一个事务里,每次一致性读都会设置并读取自己的新快照。对于锁定读,InnoDB只锁定索引记录,不包含他们之前的间隙,因此允许在锁定记录旁自由插入新记录因为禁用了间隙锁定,可能会出现不可重复读,因为其他session可以插入新行到间隙中影响:
对于UPDATE或DELETE语句,InnoDB只对它更新或删除的行持有锁。在MYSQL评估WHERE条件后,将释放不匹配行的锁,这大大降低了死锁的概率
对于UPDATE语句,如果一个行已经锁定,InnoDB执行“半一致”读,将最新提交的版本返回给MySQL,以便MySQL可以决定该行是否匹配更新的WHERE条件
e.g.可重复读VS.读已提交
CREATETABLEt(aINTNOTNULL,bINT)ENGINE=InnoDB;INSERTINTOtVALUES(1,2),(2,3),(3,2),(4,3),(5,2);COMMIT;
这种情况下,表没有索引,因此搜索和索引扫描时使用隐式的聚簇索引来进行记录锁定
#SessionA执行更新语句STARTTRANSACTION;UPDATEtSETb=5WHEREb=3;#SessionB在sessionA之后执行UPDATEtSETb=4WHEREb=2;
在InnoDB执行每个更新的时候,它先为每一行获取一个排它锁,然后决定是否修改它。如果InnoDB不修改这行,它就会释放这个锁。否则InnoDB持有锁,直到事务结束。
当使用默认的可重复读级别时,第一个UPDATE在它读取的每行上获取x锁,不释放它们中的任何一个:
x-lock(1,2);retainx-lockx-lock(2,3);update(2,3)to(2,5);retainx-lockx-lock(3,2);retainx-lockx-lock(4,3);update(4,3)to(4,5);retainx-lockx-lock(5,2);retainx-lock
第二个UPDATE在尝试获取任何锁的时候会立即阻塞(因为第一个更新在所有行上保留了锁),并且在第一次UPDATE提交或回滚之前不会继续
x-lock(1,2);blockandwaitforfirstUPDATEto
转载请注明:http://www.0431gb208.com/sjszjzl/2115.html