MySQL中大大小小的锁

1、MySQL中有哪几种锁?

按照锁的粒度,可以划分为全局锁 > 表锁 > 行锁
全局锁:对整个数据库实例加锁。
表级锁:MySQL中表级别的锁有两种,一种是表锁,一种是元数据锁(meta data lock, MDL)。
行锁:InnoDB引擎可以实现行锁,而MyISAM引擎不支持行锁。

2、全局锁

加锁命令是Flush tables with read lock(FTWRL)。一个库被锁上之后,其他的业务更新将不能进行,包括增删改数据(DML)以及修改表结构(DDL)。在客户端(异常)断开时,会自动释放锁。典型的应用场景是做全库的逻辑备份。

3、表级锁

①表锁
加锁语法是lock tables … read/write,可使用unlock tables主动释放锁,也会在客户端断开时释放锁。加锁后,不仅会限制其他线程的读写操作,还会限制本线程接下来的操作。例如,线程A lock tables t1 read, t2 write,那么线程A在解锁前,自己也只能read t1和write t2。与InnoDB所支持的行锁相比,表锁的粒度还是偏大了。因此,表锁一般是在数据库引擎不支持行锁的时候才会被用到。

②元数据锁(MDL)
MDL不需要显示使用,而是在访问一个表的时候自动加上。当对一个表做增删改查操作时,加MDL读锁;当对表结构做变更时,加MDL写锁。读锁之间不互斥,读锁和写锁之间、写锁和写锁之间互斥。事务中开启的MDL锁,在语句执行开始时申请,直到事务提交后才会释放。如果在事务过程中,执行表的变更操作,那么将阻塞后续其他事务对该表的访问行为,因为变更操作(写锁)因事务中开启的MDL而阻塞后,其他对该表的查询操作(读锁)又会因为此变更操作所持的写锁而阻塞。
上述问题的解决办法:1)尽量不要有长事务,长事务会使该事务持续长时间占用MDL锁;2)如果要做DDL变更的表刚好有长事务在执行,考虑先暂停DDL,或者kill掉这个事务。

4、行锁

事务开启后,在需要的时候会加上行锁,但是要到事务提交后才会释放行锁。因此,我们需要把事务过程中容易引起锁冲突、影响并发度的加锁语句尽量往后放,即尽量靠近commit,这样可以较大程度地减少锁等待的时间。
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,如果只是对普通索引加了锁,并且是覆盖索引不用回表的情况下,主键索引不会加锁。lock in share mode只会锁覆盖索引,而如果是for update的话,系统会认为你接下来可能会更新数据,就会顺带锁住主键索引上满足条件的行。另外,该索引不能失效,否则都会从行锁升级为表锁。

死锁:在并发系统中,各个线程之间发生资源的循环依赖,涉及线程都在等待其他线程释放资源,从而进入无限等待的状态,称为死锁。

解决死锁的策略:①直接进入等待,直到超时。但是超时时间的设置需要谨慎,太长会导致较长的等待时间,太短又容易产生误伤(即不是死锁,只是简单锁等待的情况下,会直接发生等待超时);②死锁检测。检测到发生死锁后,回滚死锁循环中的一个事务,使得循环依赖被破坏,那么其他事务就可以继续执行。

死锁检测方法的问题:当一个事务被锁的时候,就要看看它所依赖的线程有没有被别的事务锁住,如此循环,最后判断是否出现死锁。如果有1000个线程都要更新同一行,那么每个新来的线程被堵住时都要判断一下自己的加入是否会导致死锁,这个判断过程是O(n)复杂度的。整个一轮判断下来,是百万级别的。对于这种情况,死锁检测的开销是很大的。

上述问题解决办法:①对于相同行的更新,排队进入引擎,控制好数据库服务端的并发度。当服务端只有10个线程,而不是1000个线程需要进行死锁检测,那么就不会有那么大的检测开销了。②重新设计业务逻辑,将一行改成逻辑上的多行,以此来减少多个线程对同一行的更新。比如,某个账户的收钱行为,可以把这个账户分成10条记录,账户总额等于这10条记录的总和。

猜你喜欢

转载自blog.csdn.net/Longstar_L/article/details/107327333
今日推荐