MySQL 全局锁、表级锁、行级锁,你搞清楚了吗?( 二 )

需要注意的是,表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作 。
也就是说如果本线程对学生表加了「共享表锁」,那么本线程接下来如果要对学生表执行写操作的语句 , 是会被阻塞的,当然其他线程对学生表进行写操作时也会被阻塞,直到锁被释放 。
要释放表锁,可以使用下面这条命令,会释放当前会话的所有表锁:
unlock tables另外,当会话退出后 , 也会释放所有表锁 。
不过尽量避免在使用 InnoDB 引擎的表使用表锁,因为表锁的颗粒度太大,会影响并发性能,InnoDB 牛逼的地方在于实现了颗粒度更细的行级锁 。
元数据锁再来说说元数据锁(MDL) 。
我们不需要显示的使用 MDL,因为当我们对数据库表进行操作时,会自动给这个表加上 MDL:

  • 对一张表进行 CRUD 操作时,加的是 MDL 读锁;
  • 对一张表做结构变更操作的时候 , 加的是 MDL 写锁;
MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更 。
当有线程在执行 select 语句( 加 MDL 读锁)的期间,如果有其他线程要更改该表的结构( 申请 MDL 写锁),那么将会被阻塞,直到执行完 select 语句( 释放 MDL 读锁) 。
反之,当有线程对表结构进行变更( 加 MDL 写锁)的期间,如果有其他线程执行了 CRUD 操作( 申请 MDL 读锁),那么就会被阻塞,直到表结构变更完成( 释放 MDL 写锁) 。
MDL 不需要显示调用 , 那它是在什么时候释放的?
MDL 是在事务提交后才会释放,这意味着事务执行期间,MDL 是一直持有的 。
那如果数据库有一个长事务(所谓的长事务,就是开启了事务,但是一直还没提交),那在对表结构做变更操作的时候,可能会发生意想不到的事情,比如下面这个顺序的场景:
  1. 首先,线程 A 先启用了事务(但是一直不提交),然后执行一条 select 语句,此时就先对该表加上 MDL 读锁;
  2. 然后,线程 B 也执行了同样的 select 语句,此时并不会阻塞,因为「读读」并不冲突;
  3. 接着,线程 C 修改了表字段 , 此时由于线程 A 的事务并没有提交,也就是 MDL 读锁还在占用着,这时线程 C 就无法申请到 MDL 写锁,就会被阻塞 , 
那么在线程 C 阻塞后,后续有对该表的 select 语句,就都会被阻塞,如果此时有大量该表的 select 语句的请求到来,就会有大量的线程被阻塞?。馐笔菘獾南叱毯芸炀突岜?。
为什么线程 C 因为申请不到 MDL 写锁 , 而导致后续的申请读锁的查询操作也会被阻塞?
这是因为申请 MDL 锁的操作会形成一个队列,队列中写锁获取优先级高于读锁 , 一旦出现 MDL 写锁等待,会阻塞后续该表的所有 CRUD 操作 。
所以为了能安全的对表结构进行变更,在对表结构变更前 , 先要看看数据库中的长事务,是否有事务已经对表加上了 MDL 读锁,如果可以考虑 kill 掉这个长事务,然后再做表结构的变更 。
意向锁接着,说说意向锁 。
  • 在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前 , 需要先在表级别加上一个「意向共享锁」;
  • 在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」;
也就是,当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁 。
而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的 。
不过,select 也是可以对记录加共享锁和独占锁的,具体方式如下:
//先在表上加上意向共享锁 , 然后对读取的记录加共享锁select ... lock in share mode;//先表上加上意向独占锁 , 然后对读取的记录加独占锁select ... for update;意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突 , 只会和共享表锁(

推荐阅读