Ⅰ.MySQL中的锁
1.全局锁
概念
对整个数据库实例加锁。让整个库处于只读状态。开启全局锁后,其他线程的数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句将被阻塞。
命令(FTWRL)
Flush tables with read lock
使用场景
全库逻辑备份(对于使用了不支持事务引擎的表或库)
使用FTWRL命令使全库进入只读状态,然后对整个库进行备份。防止备份过程中各表视图不一致的问题。
存在的问题:
如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
如果你在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。
对于使用了支持事务隔离级别的引擎的库。可以使用官方自带的逻辑备份工具是mysqldump。当mysqldump使用参数–single-transaction的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。
如果有的表使用了不支持事务的引擎,那么备份就只能通过FTWRL方法。这往往是DBA要求业务开发人员使用InnoDB替代MyISAM的原因之一。
全库只读 为什么不使用set global readonly=true的方式
1.在有些系统中,readonly的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改global变量的方式影响面更大,我不建议你使用。
2.在异常处理机制上有差异。如果执行FTWRL命令之后由于客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为readonly之后,如果客户端发生异常,则数据库就会一直保持readonly状态,这样会导致整个库长时间处于不可写状态,风险较高。
2.表级锁
概念
MySQL里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。
表锁
lock tables … read/write
与FTWRL类似,可以用unlock tables主动释放锁
lock tables语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。
元数据锁(表结构锁)
不需要显式使用,在访问一个表的时候会被自动加上。
当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。
读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
给表加字段时可能会引发MDL锁的死锁问题
如何安全地给小表加字段?
- 首先我们要解决长事务,事务不提交,就会一直占着MDL锁。在MySQL的information_schema 库的 innodb_trx 表中,你可以查到当前执行中的事务。如果你要做DDL变更的表刚好有长事务在执行,要考虑先暂停DDL,或者kill掉这个长事务。
- 但如果你要变更的表是一个热点表,虽然数据量不大,但是上面的请求很频繁,而你不得不加个字段这时候kill可能未必管用,因为新的请求马上就来了。比较理想的机制是,在alter table语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到MDL写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者DBA再通过重试命令重复这个过程。
3.行级锁
概念
行锁是表中每一行的单独的锁,更细的锁粒度代表着更高的并发量。
行锁是由各个存储引擎自己实现的锁。
两阶段锁协议
InnoDB中,对一行进行修改操作会自动给该行加行锁。(需要的时候)并且只有在这个事务提交后行锁才会被释放。
PS:如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
例如
一个售票系统中,要操作的数据行包括:不同用户的账户余额、商家的账户余额、记录日志等等。
由于许多个用户的下单操作会导致高频的修改商家的账户信息,所以在下单的这个事务中,修改商家余额这一步应该是放到事务的最后执行。这样一来,只有到最后一步才会给商家的余额信息加锁,修改后立刻释放,保证了并发量避免了锁冲突。
4.死锁和死锁检测
出现死锁后,有两种策略:
- 直接进入等待,直到超时。这个超时时间可以通过参数
innodb_lock_wait_timeout
来设置,默认值是50s。 - 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数
innodb_deadlock_detect
设置为on
,表示开启这个逻辑。
分析:
- 第一种策略等待时间太长,如果把等待时间调短也容易误伤正常的锁等待,所以一般不使用。
- 第二种策略在发生死锁时是能够快速发现并处理的,但是也有额外消耗。如果大量线程去修改同一行(热点行问题),每次加锁时都会导致死锁检测要花费大量的时间来检测是否构成死锁,严重耗费了CPU资源并影响并发量。
怎么解决由这种热点行更新导致的性能问题
- 如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。
- 控制并发度。(通过中间件或修改MySQL源码)
- 将一行数据改成逻辑上的多行来减少锁冲突。