MySQL源码学习(三) Group Commit
承接MySQL源码学习(一) 从一次insert开始,本文会详细展开说明其中流程之一——Group Commit
Group Commit是MySQL 5.6版本加入的特性,目的是为了提高事务的并发度,并以此提高MySQL的性能。
Group Commit的原理
在多线程并发中,如果必须对资源的操作进行加锁,开发者最先想到的流程就是在资源操作前获取锁,操作完以后释放锁。比如修改一个临界区或者互斥区的内存空间,如果不修改整体的业务逻辑,几乎无法对其进行优化。在MySQL中,事务提交时,为保证日志的顺序化写入,如果只是简单的:写日志->提交事务逻辑,几乎无法进行优化。为了继续提高性能,写MySQL的大牛们想出了两阶段事务提交的逻辑(两阶段事务不仅仅能提高性能,还能解决分布式事务的一致性,redo log和binlog的一致性等问题),即在事务提交的第一阶段,把日志先写到redo log缓冲区,第二阶段时,再把日志刷到存储介质中,然后提交事务。而组提交在其中发挥的作用是:在第一阶段时,多个并发线程可以把日志都写到一个缓冲中,这些等待刷到磁盘的日志组成一个group;第二阶段时,只要group中的任意一个事务的日志要刷新时,便会把整个缓冲区中的日志都刷新到磁盘上。这样就实现了将多次磁盘的顺序IO操作变为了1次磁盘IO,写性能上得到了质的飞跃。
Group Commit能大幅提升性能的核心原因
往往电影和小说会把这类巨大的技术突破,表现为科学家、工程师们一觉睡醒后的灵光一现,或者遭遇了重大挫折后的神来之笔。这样才能让我们这些吃瓜观众们感受到成功原来距离我们如此之近,从而达到励志效果。然而事实往往相反,这样的设计,其实是通过工程师们长时间的分析尝试,和孜孜不倦的辛苦测试得来的。
MySQL 5.6版本之前采取的是传统的加锁逻辑,性能瓶颈在于为保证事务的一致性,redo log只能顺序io,而不能随机io。那么在一个线程完成之前,其他线程只能等待。基于这样的背景,我们会自然而然地想到是否可以把多次IO变为1次IO,如果需要多次变1次,那么自然就需要一个缓冲区。因此,Group Commit能够提升性能的核心原因其实就是:磁盘的写缓冲。一个朴实的功能,实际上经历了多年时间,从5.0一直到5.6才得以真正实现。
Group Commit的代码
log_write_up_to函数中,可以看到一段及其不自然的代码,如图1所示。这很像是后面才加进去的代码,其中1303 行~1347行便是group的处理逻辑。
图1 group commit在log_write_up_to函数中的处理
log_sys是redo log的日志管理对象。其中:
buf_next_to_write: ulint类型,还没有写入文件的缓存的偏移量。例如,当日志文件分割时,可能有的缓存就要写到下一个文件中。
buf_free: ulint类型,存放了可写缓冲区的偏移量。表征了可写缓冲区的头部。既然是缓冲区,里面必然可能存在还没有刷新的数据。
write_lsn:当前日志缓冲区中的lsn
write_buf:要写入的缓冲区
冲代码的1332行至1340行可以看出,并非可有多少内容就写多少内容。缓冲内容是要按照文件系统的块大小类分配的。OS_FILE_LOG_BLOCK_SIZE为512,因此是要按照512字节来对齐的。
最后,log_mutex_exit。log mutext是整个redo log日志的锁,锁流程中,只包含了日志偏移的计算,并没有日志的写入过程,对整个日志的。整体速度大大加快。
再看buf和磁盘刷入的流程,如图2所示:
图2 日志缓冲的写入和写磁盘
这里可以看到,日志被写入缓冲后,日志的写锁就被释放。最终写入磁盘时(写入磁盘是事务提交的第二阶段执行,也就是说事务提交的两个阶段都会进入到log_write_up_to函数),日志管理系统便没有锁,当一个group在写时,另一个group的日志又被并行的写入了缓冲,以此循环,实现了缓冲和刷盘的并行,大大减少了等待的时间。这便是group commit的精妙之处。
- 点赞
- 收藏
- 关注作者
评论(0)