mysql引擎 时序_MySQL · 引擎特性 · InnoDB 同步机制

前言

现代操作系统以及硬件基本都支持并发程序,而在并发程序设计中,各个进程或者线程需要对公共变量的访问加以制约,此外,不同的进程或者线程需要协同工作以完成特征的任务,这就需要一套完善的同步机制,在Linux内核中有相应的技术实现,包括原子操作,信号量,互斥锁,自旋锁,读写锁等。InnoDB考虑到效率和监控两方面的原因,实现了一套独有的同步机制,提供给其他模块调用。本文的分析默认基于MySQL 5.6,CentOS 6,gcc 4.8,其他版本的信息会另行指出。

基础知识

同步机制对于其他数据库模块来说相对独立,但是需要比较多的操作系统以及硬件知识,这里简单介绍一下几个有用的概念,便于读者理解后续概念。

内存模型 :主要分为语言级别的内存模型和硬件级别的内存模型。语言级别的内存模型,C/C++属于weak memory model,简单的说就是编译器在进行编译优化的时候,可以对指令进行重排,只需要保证在单线程的环境下,优化前和优化后执行结果一致即可,执行中间过程不保证跟代码的语义顺序一致。所以在多线程的环境下,如果依赖代码中间过程的执行顺序,程序就会出现问题。硬件级别的内存模型,我们常用的cpu,也属于弱内存模型,即cpu在执行指令的时候,为了提升执行效率,也会对某些执行进行乱序执行(按照wiki提供的资料,在x86 64环境下,只会发生读写乱序,即读操作可能会被乱序到写操作之前),如果在编程的时候不做一些措施,同样容易造成错误。

内存屏障 :为了解决弱内存模型造成的问题,需要一种能控制指令重排或者乱序执行程序的手段,这种技术就叫做内存屏障,程序员只需要在代码中插入特定的函数,就能控制弱内存模型带来的负面影响,当然,由于影响了乱序和重排这类的优化,对代码的执行效率有一定的影响。具体实现上,内存屏障技术分三种,一种是full memory barrier,即barrier之前的操作不能乱序或重排到barrier之后,同时barrier之后的操作不能乱序或重排到barrier之前,当然这种full barrier对性能影响最大,为了提高效率才有了另外两种:acquire barrier和release barrier,前者只保证barrier后面的操作不能移到之前,后者只保证barrier前面的操作不移到之后。

互斥锁 :互斥锁有两层语义,除了大家都知道的排他性(即只允许一个线程同时访问)外,还有一层内存屏障(full memory barrier)的语义,即保证临界区的操作不会被乱序到临界区外。Pthread库里面常用的mutex,conditional variable等操作都自带内存屏障这层语义。此外,使用pthread库,每次调用都需要应用程序从用户态陷入到内核态中查看当前环境,在锁冲突不是很严重的情况下,效率相对比较低。

自旋锁 :传统的互斥锁,只要一检测到锁被其他线程所占用了,就立刻放弃cpu时间片,把cpu留给其他线程,这就会产生一次上下文切换。当系统压力大的时候,频繁的上下文切换会导致sys值过高。自旋锁,在检测到锁不可用的时候,首先cpu忙等一小会儿,如果还是发现不可用,再放弃cpu,进行切换。互斥锁消耗cpu sys值,自旋锁消耗cpu usr值。

递归锁 :如果在同一个线程中,对同一个互斥锁连续加锁两次,即第一次加锁后,没有释放,继续进行对这个锁进行加锁,那么如果这个互斥锁不是递归锁,将导致死锁。可以把递归锁理解为一种特殊的互斥锁。

死锁 :构成死锁有四大条件,其中有一个就是加锁顺序不一致,如果能保证不同类型的锁按照某个特定的顺序加锁,就能大大降低死锁发生的概率,之所以不能完全消除,是因为同一种类型的锁依然可能发生死锁。另外,对同一个锁连续加锁两次,如果是非递归锁,也将导致死锁。

原子操作

现代的cpu提供了对单一变量简单操作的原子指令,即这个变量的这些简单操作只需要一条cpu指令即可完成,这样就不用对这个操作加互斥锁了,在锁冲突不激烈的情况下,减少了用户态和内核态的切换,化悲观锁为乐观锁,从而提高了效率。此外,现在外面很火的所谓无锁编程(类似CAS操作),底层就是用了这些原子操作。gcc为了方便程序员使用这些cpu原子操作,提供了一系列__sync开头的函数,这些函数如果包含内存屏障语义,则同时禁止编译器指令重排和cpu乱序执行。

InnoDB针对不同的操作系统以及编译器环境,自己封装了一套原子操作,在头文件os0sync.h中。下面的操作基于Linux x86 64位环境, gcc 4.1以上的版本进行分析。

os_compare_and_swap_xxx(ptr, old_val, new_val)类型的操作底层都使用了gcc包装的__sync_bool_compare_and_swap(ptr, old_val, new_val)函数,语义为,交换成功则返回true,ptr是交换后的值,old_val是之前的值,new_val是交换后的预期值。这个原子操作是个内存屏障(full memory barrier)。

os_atomic_increment_xxx类型的操作底层使用了函数__sync_add_and_fetch,os_atomic_decrement_xxx类型的操作使用了函数__sync_sub_and_fetch,分别表示原子递增和原子递减。这个两个原子操作也都是内存屏障(full memory barrier)。

另外一个比较重要的原子操作是os_atomic_test_and_set_byte(ptr, new_val),这个操作使用了__sync_lock_test_and_set(ptr, new_val)这个函数,语义为,把ptr设置为new_val,同时返回旧的值。这个操作提供了原子改变某个变量值的操作,InnoDB锁实现的同步机制中,大量的用了这个操作,因此比较重要。需要注意的是,参看gcc文档,这个操作不是full memory barrier,只是一个acquire barrier,简单的说就是,代码中__sync_lock_test_and_set之后操作不能被乱序或者重排到__sync_lock_test_and_set之前,但是__sync_lock_test_and_set之前的操作可能被重排到其之后。

关于内存屏障的专门指令,MySQL 5.7提供的比较完善。os_rmb表示acquire barrier,os_wmb表示release barrier。如果在编程时,需要在某个位置准确的读取一个变量的值时,记得在读取之前加上os_rmb,同理,如果需要在某个位置保证一个变量已经被写了,记得在写之后调用os_wmb。

条件通知机制

条件通知机制在多线程协作中非常有用,一个线程往往需要等待其他线程完成指定工作后,再进行工作,这个时候就需要有线程等待和线程通知机制。Pthread_cond_XXX类似的变量和函数来完成等待和通知的工作。InnoDB中,对Pthread库进行了简单的封装,并在此基础上,进一步抽象,提供了一套方便易用的接口函数给调用者使用。

系统条件变量

在文件os0sync.cc中,os_cond_XXX类似的函数就是InnoDB对Pthread库的封装。常用的几个函数如:

os_cond_t是核心的操作对象,其实就是pthread_cond_t的一层typedef而已,os_cond_init初始化函数,os_cond_destroy销毁函数,os_cond_wait条件等待,不会超时,os_cond_wait_timed条件等待,如果超时则返回,os_cond_broadcast唤醒所有等待线程,os_cond_signal只唤醒其中一个等待线程,但是在阅读源码的时候发现,似乎没有什么地方调用了os_cond_signal。。。

此外,还有一个os_cond_module_init函数,用来window下的初始化操作。

在InnoDB下,os_cond_XXX模块的函数主要是给InnoDB自己设计的条件变量使用。

InnoDB条件变量

如果在InnoDB层直接使用系统条件变量的话,主要有四个弊端,首先,弊端1,系统条件变量的使用需要与一个系统互斥锁(详见下一节)相配合使用,使用完还要记得及时释放,使用者会比较麻烦。接着,弊端2,在条件等待的时候,需要在一个循环中等待,使用者还是比较麻烦。最后,弊端3,也是比较重要的,不方便系统监控。

基于以上几点,InnoDB基于系统的条件变量和系统互斥锁自己实现了一套条件通知机制。主要在文件os0sync.cc中实现,相关数据结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值