文章摘要(AI生成)
InnoDB引擎被广泛使用的原因主要有以下几点:
1. 支持事务:InnoDB是一个支持ACID事务的存储引擎,可以保证数据的一致性、隔离性、持久性等特性,适用于需要高并发读写操作的数据库场景。
2. 缓冲池:InnoDB引擎通过缓冲池来提高数据库的整体性能。缓冲池是一个内存区域,用于缓存磁盘上的数据页,减少了磁盘IO的次数,加快了数据读取和写入速度。
3. 插入缓冲:InnoDB引擎使用插入缓冲来提升数据插入性能。在插入数据时,会先将次要索引的更新记录到插入缓冲中,再通过合并操作提升次要索引的插入性能。
4. 自适应哈希索引:InnoDB引擎可以根据查询的频率和模式,为热点页建立自适应哈希索引,提高连续查询的性能。
5. 锁信息和数据字典信息:InnoDB引擎可以保证并发访问时的线程安全,并且缓存了数据表信息,提升了数据访问的效率。
6. 重做日志缓冲:InnoDB在缓冲池中变更数据时,会先将变更写入重做日志缓冲中,然后再按时或事务提交时写入磁盘,确保数据的持久性。可以通过配置属性控制重做日志的写入时机,提高性能。
综上所述,InnoDB引擎通过支持事务、优化内存结构和提供多种性能优化机制,使得它成为了很多数据库开发人员首选的存储引擎。
我们在建表时,很多时候都选用InnoDB引擎,为什么使用InnoDB呢?很主要的一个点在于它支持事务
InnoDB内存结构
InnoDB由缓冲池、额外内存池、双写和重做日志构成。如下:
buffer pool缓冲池
为了解决CPU读写速度和磁盘读写速度的鸿沟,基于磁盘的数据库系统通常使用缓冲池记录来提高数据库的的整体性能。缓冲池的大小直接影响着数据库的整体性能,可以通过配置参数 innodb_buffer_pool_size
来设置。
缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、 自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)和数据字典信息(data dictionary)。
数据页和索引页:提升数据读取性能。innoDB工作时,需要以页为最小单位去将磁盘中的数据加载到内存中,与数据库相关的所有内容都存储在Page结构里。数据存储在数据页,索引存储在索引页。
插入缓冲:提升插入性能。针对数据插入时,次要索引更新前先判断插入缓冲中是否存在要插入的次要索引,如果存在则更新插入缓冲中的次要索引,再通过一定频率和条件对插入缓冲和对应索引页中的次要索引进行合并操作。通过多次插入合并到一个操作来提升次要索引的插入性能。
自适应哈希索引:提高连续查询性能。InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。自适应哈希需要保证数据连续查询时,条件中所用字段相同。当访问次数大于100或访问次数大于页中所有记录的1/16时,会创建自适应哈希索引。
锁信息:保证资源并发访问时的线程安全
数据字典信息:缓存数据表信息。数据字典是对数据库中的数据、库对象、表对象等的元信息的集合。
额外内存池
额外内存池是InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间
重做日志缓冲
缓存重做日志redoLog。InnoDB在缓冲池中变更数据时,会首先将相关变更写入重做日志缓冲中,然后再按时或者当事务提交时写入磁盘。可以通过innodb_flush_log_at_trx_commit
属性控制redoLog写入磁盘的时机:
- 当属性值为0时,事务提交时,不会对重做日志进行写入操作,而是等待主线程按时写入每秒写入一次;
- 当属性值为1时,事务提交时,会将重做日志写入文件系统缓存,并且调用文件系统的fsync,将文件系统缓冲中的数据真正写入磁盘存储,确保不会出现数据丢失;
- 当属性值为2时,事务提交时,也会将日志文件写入文件系统缓存,但是不会调用fsync,而是让文件系统自己去判断何时将缓存写入磁盘。
双写缓冲
缓存脏页(缓存中修改后的,和磁盘不一致的数据页),由系统将缓冲池中的脏页写入磁盘。
检查点机制
InnoDB内存缓冲池中的数据page要完成持久化的话,是通过两个流程来完成的,一个是脏页落盘;一个是预写redo log日志。而重做日志和缓冲池并不能无限大。所以需要通过检查点机制来解决:
- 缩短数据库恢复时间:数据库关闭重启时,不需要重做所有日志,只需要对检查点之后的日志进行恢复
- 缓冲池满时,脏页刷新到磁盘:通过LRU算法淘汰缓存中的数据页,如果为脏页则执行数据落盘
- 重做日志不可用,刷新脏页:保证缓冲池中的页刷新到当前重做日志的位置
根据触发时间不同,检查点分为两种:sharp checkpoint(关闭数据库时脏页落盘)和fuzzy checkpoint(正常运行时的脏页落盘)。
InnoDB事务管理
事务是用来保证一致性的机制,我们可以通过下面这个转账和查询的两个并发操作来分析下如何保证数据一直性。
假如有用户A和用户B,用户B负责将A账户的钱转到B账户,而用户A则负责查询账户总额,在用户A的角度里,会统计到账户总额与用户B操作前的账户总额不一致。
针对上述过程,我们可以通过对A和B操作加锁来保证数据强一致性,用户A在读取时对账户A加锁,用户B在转账时需要等待用户A释放,这种并发控制机制即是基于锁的并发控制(LBCC)。然而加锁一方面会降低数据库的性能,另一方面也会导致死锁。
另一种解决方案是我们对数据的变更记录加上版本记录,当用户A进行读取操作时,只能读取到其所在事务中的数据快照,这样的并发控制机制我们成为多版本并发控制(MVCC)
InnoDB的mvcc实现
MVCC在mysql中的实现依赖的是undo log与read view 。
undoLog
InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的事务ID,一个保存了行的回滚指针。每开始一个新的事务,都会自动递增产生一个新的事务id。而回滚指针则指向undo log中修改前的行记录。一个插入和更新操作后产生的undo log如下所示:
readView
对于使用 READ UNCOMMITTED 隔离级别的事务来说,直接读取记录的最新版本就好了。
对于使用 SERIALIZABLE 隔离级别的事务来说,使用加锁的方式来访问记录。
对于使用 READ COMMITTED 和 REPEATABLE READ 隔离级别的事务来说,需要版本链来访问记录
而read view则是对版本链的实现,它存储了当前数据库中的所有活跃事务,存储了一个活跃事务id列表m_ids,约定了如下三个变量:
m_up_limit_id:m_ids事务列表中的最小事务id,如果当前列表为空那么就等于m_low_limit_id
m_low_limit_id:系统中将要产生的下一个事务id的值。
m_creator_trx_id:当前事务id,m_ids中不包含当前事务id。
当在事务n访问某条记录时,只需按照如下步骤来判断版本链中的记录是否可见:
- 如果被访问版本的 trx_id 属性值小于 m_up_limit_id ,表明生成该版本的事务在生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的 trx_id 属性值等于 m_creator_trx_id 既当前事务id,可以被访问。
- 如果被访问版本的 trx_id 属性值大于等于 m_low_limit_id ,在生成 ReadView 后才生成,所以该版本不可以被当前事务访问。
- 如果被访问版本的 trx_id 属性值在m_up_limit_id 和 m_low_limit_id 之间,那就需要判断一下trx_id属性值是不是在 m_ids 列表中。
- 如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;
- 如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
ReadView的操作时机
READ COMMITTED
如果数据库事务隔离机制为READ COMMITTED,则在每次读取数据前都生成一个ReadView。
例如如下case:
我们在事务3中执行两次查询id为1的记录
第一次查询时,在生成readView后,首先会读取版本链的第一条,发现事务id为当前事务id,可以访问。
第二次查询时,在生成readView后,首先会读取版本链的第一条,发现事务id在活跃事务范围,但不在活跃事务列表中,可以访问。
第一次和第二次查询结果会不一致,是因为中间会有其他事务提交,读取到了其他事务提交的结果,造成了不可重复读。
REPEATABLE READ
如果数据库事务隔离机制为REPEATABLE READ,在事务开始后第一次读取数据时生成一个ReadView
还是上述case,在REPEATABLE READ下,就演变为:
我们在事务3中执行两次查询id为1的记录
第一次查询时,在生成readView后,首先会读取版本链的第一条,发现事务id为当前事务id,可以访问。
第二次查询时,在生成readView后,首先会读取版本链的第一条,发现事务id在活跃事务范围且在活跃事务列表中,所以不能访问;访问下一条发现事务id为当前事务id,可以访问.
第一次和第二次查询结果一致,避免了不可重复读。但是插入和删除后仍会读取到,造成幻读。
当前读和快照读
在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。
快照读:读取的是记录的可见版本 (有可能是历史版本),不用加锁。
当前读:读取的是记录的最新版本,并且当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
事务回滚和恢复
redo log用于在崩溃时恢复数据,undo log用于对事务的影响进行撤销,也可以用于多版本控制。当事务COMMIT时,必须先将该事务的所有日志都写入到redo log文件进行持久化之后,COMMIT操作才算完成。
评论区