根据加锁的范围,MySQL里面的锁可分为全局锁、表锁、行锁这三类。
目录
全局锁
它是对整个数据库的实例进行加锁。如果你想去加一个全局读锁可以通过:Flush tables with read lock;(也就是我们常说的FTWRL)这条语句进行加锁,它可以让我们的数据库处于一种只读状态,当有其他线程对这个数据库实例进行增删查的操作、或者是建表删表等修改数据库数据的操作的时候会被阻塞。
这样我们在备份的时候,可以保持一致性。
这个就相当于我们在可重复读隔离级别下开启一个事务,虽然现在5.6之后默认的引擎是InnoDB,但是有些人或者公司还用的MyISAM引擎,这种引擎是不支持事务的。所以,这个FTWRL就非常重要了。
当然,我们也可以将global变量设置为readonly,也可以达到相应的效果。但是这种情况下,如果客户端发生异常,数据库会一直保存readonly状态,导致数据库长时间处于不可写状态,风险太高。而FTWRL在客户端出现异常的情况下,MySQL会自动释放这个全局锁,整个库就可以回到正常更新的状态。
表级锁
有两种表级锁,一种是我们常说的表锁,一种是元数据锁(也就是MDL)。
表锁
lock tables ... read/write。它可以显式的释放锁,我们可以用unlock tables 去主动地释放锁,它与FTWRL类似、当我们的客户端出现异常或者客户端断开的时候自动的去释放锁。而且表锁在限制其它线程的读写之外也限制了本线程的操作对象。当然,我们现在的InnoDB一般不会用这种锁,因为范围太大。它有粒度更细的锁——行锁。
MDL
它不需要显式的调用,当我们在访问一个表的时候会自动加上,它就是为了保证读写的正确性。当我们在对表进行增删改查的时候会加上一个MDL读锁,当我们对表的结构进行改变的时候会加入MDL写锁,它的读写锁与Java中的读写锁比较类似,读锁之间是不排斥的,读锁与写锁、写锁与写锁是互斥的,这样可以保证变更表结构的安全性。当一个线程需要给表加字段,首先需要拿到MDL的写锁,然后其他的线程对这张表的增删改查或者更改表的结构的操作都会被阻塞到第一个线程释放锁。
其实,当我们在增加字段的时候千万要小心,要不然可能一个增加字段的操作就可能导致整个库直接挂了。
如果先有线程A去对这个表进行增删改查,在读锁还未释放的情况下,线程B对这个表进行增加字段的操作,线程C对这个表进行增删改查。因为线程A最先对表进行增删改查所以它先获取了MDL读锁,然后在读锁还未释放的情况下,线程B想要修改表的结构,这样它就会被阻塞到线程A释放锁为止,然后线程C也会因为线程B的阻塞而陷入阻塞,因为线程B需要获取写锁,也会导致其它线程对这个表不可读写。因为一般情况下会有重试机制,不一会,数据库的线程就会被用完,导致整个数据库挂掉。
行锁
行锁是在引擎层由各个引擎自己实现的,但是并不是所有引擎都支持行锁。比如MyISAM就不支持行锁,这也是MyISAM被InnoDB取代的一个原因。
那么什么是行锁,行锁就是对数据表中的行记录的锁,如果事务A更新一行,事务B需要更新同一行的话就会被阻塞,知道事务A提交之后才能更新。
两阶段锁
在InnoDB中有一个两阶段锁的协议,就是在事务中,行锁在获取的时候才会被加上,但并不代表着它随时都可以被释放,而是等事务提交后才会被释放。如果事务A先获取了行锁,事务B要对同一行进行修改,那么只有等事务A 提交后,事务B才能进行获取锁。
所以我们在日常中,如果你的事务中需要锁多个行,那么你就需要把可能造成锁冲突的锁往后放。
死锁
死锁这个词是非常常见的一个词,简单地说就是A持有B的资源等待B的释放,B持有A的资源等待A的释放,这就成了一个无限等待的问题了,导致A与B的资源都不能被回收。在数据库中就是事务A与事务B同时开始,事务A先修改id=1、在修改id=2的数据,事务B先修改id=2、在修改id=1的数据,这样的话事务A在等待事务B释放id=2的行锁,事务B在等待事务A释放id=1的行锁。然后就陷入了死循环,进入死锁状态。
在解决死锁状态时,有一个近乎通用的方法就是加超时时间,让它们等待,一直到等待超时后再释放,MySQL里的超时时间是通过 Innodb_lock_wait_timeout来进行设置,它默认为50s。如果发生了死锁之后第一个锁被锁住50s之后才能被释放。但是50s对于在线服务来说是无法接受的,因为等待时间太长了。
所以我们有另外一种方法主动死锁检测。
它是发起了死锁检测,在发现了死锁之后,主动回滚死锁链中的某一个事务,然后释放锁,让其他事务能够顺利进行下去。我们在设置的时候就是 将 innodb_deadlock_detect设置为on就可以了。