MySQL基础第五篇

131 阅读6分钟

MySQL锁 根据加锁的范围,MySQL里可以分为:全局锁,表级锁,行级锁

1.全局锁

对整个数据库实例加锁,MySQL对于加全局锁的方法:Flush tables with read lock(FTWRL)

这个命令可以是整个数据库实例处于只读状态,使用了改命令之后,所有的增删改类语句操作都会被阻塞

使用场景:一般为全库的逻辑备份

风险:如果在主库备份,在备份期间不能增删改,业务停摆;如果在从库上备份的话,备份期间不能执行主库的binlog,导致主从延迟

官方自带的逻辑备份工具mysqldump,当mysqldump使用参数--single-transaction的时候,会启动一个事务,确保拿到一致性的视图。而由于MVCC的支持,这个过程数据是可以正常更新的

有了这个逻辑备份工具那为什么还要FTWRL呢?一致性读是好,但是前提是引擎要支持这个隔离级别,如MyISAM这种不支持事物的引擎,如果在备份过程中有更新,那么他只能拿到最新的数据,破坏了备份的一致性,这种时候就需要FTWRL了。所以single-transacation这种方法只适用于支持事物的数据库引擎

那既然只要只读,那为什么不用set global readonly = true呢?因为在一些系统中,readonly的值会被用来做其他逻辑,比如用来判断是主库还是从库,因此修改global变量的方式影响范围太大;在异常处理的方式有差异,如果在执行FTWRL命令之后,数据库发生异常断开,那么MySQL会释放掉这个全局锁,整个库会回到一个正常的状态,但是readonly之后,如果客户端发生异常,数据库会一直保存着readonly这个状态,导致整个库处于不可写的状态,风险太高;如果有用户有超级(super)权限的时候,readonly是会失效的

业务的更新不只是增删改数据(DML),还有可能是加字段等修改表结构的操作(DDL)

2.表级锁

MySQL中行级锁有两种:一是表锁,另一种是元数据锁(meta data lock,MDL)

表锁的语法是lock tables ... read/write 与FTWRL类似,可以用unlock tables主动释放锁,也可以在客户端断开的时候释放锁。需要注意lock tables语法出了会限制别的线程读写外,也限定了本线程接下来的操作对象,比方我在线程a上执行了lock tables t1 read, t2 write,那其他线程写t1,读t2的语句都会被阻塞。同时线程a在执行unlock tables之前,也只能执行读t1,读写t2的操作

元数据锁(MDL)作用在于保证读写的正确性,他不需要显示的使用,他会在访问一个表的时候自动加上,在MySQL5.5版本中引入了MDL,当我们对表做增删改查操作的时候,加MDL读锁,当对表结构进行操作的时候,加MDL写锁

读锁不互斥,所以可以多个线程同时对一张表进行增删改查。读写锁之间是互斥的,为了保证变更表结构时的安全性,所以在两个操作同时进行时,一个操作需要等待另一个操作进行完才能执行

但是当我进行一个读操作a时,我下一个读操作b来了,读锁之间不互斥,可以正常读,但是我现在又来了一个写操作c,但这时读操作a的MDL读锁还未释放,由于读写锁互斥,导致这时写操作c就会blocked,这时又来了个写操作d,因为对于表的增删改查都需要先拿读锁,这样就导致这张表完全不可读写了

那如果给表加字段呢?首先需要解决的是长事物,事务不提交,那就会一直占用这MDL锁,我们可以通过查看information_schema库的innodb_trx表中查看当前的事物,可以根据情况暂停DDL或者之间kill掉这个长事物

那如果这张表请求很频繁导致kill掉一个后紧接着又来了一个呢?比较理想的机制是,在 alter table 语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程

MariaDB 已经合并了 AliSQL 的这个功能,所以这两个开源分支目前都支持 DDL NOWAIT/WAIT n 这个语法。

ALTER TABLE tbl_name NOWAIT add column ... ALTER TABLE tbl_name WAIT N add column ...

3.行级锁

针对数据库中行记录的锁,行锁是在引擎层游各个引擎实现的,并不是所有引擎都支持行锁,如MyISAM引擎就不支持,不支持意味着只能使用表锁,会影响并法度

两阶段锁,在innoDB事物中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放了,而是要等到事物结束时才释放

建议:如果你的事物中需要锁多个表,要把最可能造成冲突,最可能影响并发的操作往后放

死锁:当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放锁时,就会导致死锁

那如何解决死锁呢?

1.通过参数innodb_lock_wait_timeout根据实际业务场景来设置超时时间,innodb默认的超时时间是50s

2.发起死锁检测,发现死锁后,主动回滚死锁链中的某一个事物,让其他事物继续执行,将参数innodb_deadlock_detect设置为on,表示开启死锁检测(默认是开启的)

如何解决热点行更新导致的性能问题?

1.如果你能确保这个事物不会发生死锁,可以暂时关闭死锁检测(不推荐)

2.控制并发度,对应相同的更新,在进入引擎之前排队,这样引擎内部就不会有很多死锁检测来占用cpu了

3.将热点更新的行数据进行拆分,通过拆分行数据的方式来减少由于对同一行造成的锁冲突,但是业务逻辑复杂度会大大提高

innodb行级锁是根据索引实现的,若更新的列没有设置索引,那么是会锁住整张表的