根据加锁的范围,MySQL 里面的锁大致可以分成全局锁、表级锁和行锁三类。
全局锁
全局锁是对整个数据库实例加锁。MySQL 的命令是 Flush tables with read lock (FTWRL)
(需要引擎支持一致性读这个隔离级别),之后整个库处于只读状态。其典型使用场景是:做全库逻辑备份。
加锁备份:
官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数 –single-transaction 的时候,导数据之前就会启动一个事务(需要使用事务引擎),来确保拿到一致性视图。
其实还有一个方式可以实现全库只读:set global readonly=true 。但是:
- 该值有时候还会用来做其他逻辑,比如判断主备库,影响面更大。
- 在异常处理机制上:FTWRL 后由客户端异常断开,那么 MySQL 会自动释放这个全局锁,回到可以正常更新的状态;如果是修改 readonly 后由客户端异常断开,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。
业务的更新
- 增删改数据(DML)
- 修改表结构的操作(DDL)
表级锁
MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)。
表锁
表锁的语法是 lock tables … read/write (unlock tables 主动释放锁)。表锁一般是在数据库引擎不支持行锁的时候才会被用到的。
需要注意,lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对象。比如,线程 A
lock tables t1 read, t2 write;
,那么 A 在执行unlock tables
之前,只能执行读 t1 & 读写 t2 的操作,不能写 t1 也不能访问其他表。
在还没有出现更细粒度的锁的时候,表锁是最常用的处理并发的方式。而对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面还是太大。
元数据锁 MDL
MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是,保证读写的正确性。
注意元数据锁也可能会引起大影响,这里举例:
这里的顺序可以这样梳理:
- Seesion A 启动,对表 t 加 MDL 读锁。可见,MDL 在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放。
- Session B 需要的也是 MDL 读锁,因此可以正常执行。
- Session C 会被 blocked,因为 Session A 的 MDL 读锁还没有释放,只能被阻塞。
- Session D 申请 MDL 读锁的请求也会被 Session C 阻塞。
假如表查询频繁,且客户端有重试机制(超时后发起新 Session 再请求),这个库的线程很快就会爆满。
问:如何给热点表加字段?
答:比较理想的机制是,在 alter table 语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到 MDL 写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者 DBA 再通过重试命令重复这个过程。