MySQL锁

加锁范围:全局锁,表级锁,行锁;

全局锁:对整个数据库实例加锁,MySQL全局锁的方法,让整个库处于只读状态的时候,可使用这个命令;线程被阻塞,数据更新语句(增删改查,DML),数据定义语句(修改表结构,建表,DDL)和更新类事务的提交语句。全局锁,做全库逻辑备份,就是把整库每个表都 select 出来存成文本。

整库备份都只读?
主库上备份,那么期间都不能执行更新, 业务停摆;
从库上备份,那么备份期间从库不能执行主库同步的binlog,会导致主从延迟;

为什么全局锁不太好?

假设关注的是用户账户余额表和用户课程表。现在发起一个逻辑备份。假设备份期间,有一个用户,他购买了一门课程,业务逻辑里就要扣掉他的余额,然后往已购课程里面加上一门课;
可以看到,这个备份结果里,用户 A 的数据状态是“账户余额没扣,但是用户课程表里面已经多了一门课”。如果后面用这个备份来恢复数据的话,用户 A 就发现,自己赚了。

官方自带逻辑备份是:mysqldump,当mysqldump使用-–single-transaction的时候,导事务之前就会启动一个事务,来确保一致性视图,而且MVCC的支持,这个过程可以正常更新;,对于不支持事务的引擎,如果备份过程有更新,总是只能取到最新数据,就破坏备份的一致性,这个时候需要使用FTWRL命令。single-transaction 方法只适用于所有的表使用事务引擎的库,否则那么备份就只能通过 FTWRL 方法;

set global readonly=true,1.也支持全库只读,客户端发生异常断开,那么会自动释放开这个全局锁,风险高;2.readonly的值被用来做其他逻辑,比如判断一个是库是主还是备库;

表级锁: :一种是表锁,一种是元数据锁;
语法是,lock table ...read/write ,与FTWRL 类似,可以用unlock table主动释放锁,客户端断开会自动释放;

举例:某个线程A中执行lock table t1 read,t2 write,那么这个语句,则其他线程写t1,读写t2,的时候都会被阻塞,同时线程A,执行unlock tables之前,也只能执行读t1,读写t2的操作。连写t1都不允许,自然不能访问其他表。

另一种表级的锁是MDL,在访问表的时候会被自动加上,保证读写的正确性,如果查询正在遍历一个表的数据,执行期间另一个线程对这个表结构变更,删了一列,那么查询线程拿到的结果跟表结构对不上,肯定不行;(MySQL5.5,引入MDL,当对表做增删改查的时候加MDL读锁,对表做结构变更加MDL写锁);

  • 读锁之间不互斥,多个线程同时对一张表增删改查;
  • 读写锁之间,写锁互拆的,用来保证变更表结构操作的安全性,如果一个线程给一个表加字段,其中一个要等另一个执行完才能开始执行。
    MDL锁是系统默认加,但却不能忽略机制
    给小表加个字段,导致整个库挂了,给表加字段,或者修改字段,加索引,需要扫描全表数据的,在操作大表需要谨慎!

1.session A先启动,会对表t加入MDL读锁,由于session b需要的也是MDL读锁,因此正常执行;
2.session c会被blocked,因为session a的MDL读锁还没有释放,而session c 需要MDL写锁,只能被阻塞;session c 之后需要在表申请MDL读锁请求也被session c阻塞,所有对表的增删改查,都需要申请MDL读锁,都会被锁住,等于这个表完全不可读写了;
如果表查询频繁,而且客户端重试机制,超时会在启一个新的session请求,这个库的线程很快就很爆满

那么安全给小表加字段呢?

  • 首先需要解决长事务,事务不提交,会一直占用MDL锁,在MySQL如果你要做 DDL 变更的表刚好有长事务在执行,要考虑先暂停 DDL,或者 kill 掉这个长事务。但是kill未必管用,新的请求马上过来比较理想的机制是,在alter table 里面设定等待时间,指定的等待时候里面能够拿到MDL写锁最好,拿不到也不要阻塞后面业务语句,先放弃;

行锁: :innodb支持行锁;
行锁针对数据表中行记录的锁,比如事务A更新一行,而事务B也更新同一行,必须等事务A操作完成才能更新。

  • 两阶段锁

1.实际上B的update语句会被阻塞,知道事务A执行commit之后,B事务才能继续执行;
2.innodb事务中,行锁在需要时候才加上,但不是不需要立刻释放,而是事务结束时才释放,这个就是两阶段锁协议;

如何死锁和死锁检测?
并发系统中不同线程会导致循环资源依赖,线程等待别的线程释放资源时,就会导致这几个线程无限等待状态;称为死锁

事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁,事务A和事务B在互相等待对方的资源释放,就进入死锁状态;
两种策略:
1.设置这个超时时间可以通过innodb_lock_wait_timeout来设置。
2.发起死锁检测,发生死锁后,主动回滚死锁链条中的某一个事务,让其他事务继续执行,将参数innodb_lock_wait_timeout设置为on,表锁开启这个逻辑。超时时间太小,很快解开会导致误伤。
3.主动死锁检测,innodb_deadlock_detect默认值是on,主动死锁检测发生的时候,能够快速发现并且处理,不过有额外负担;
死锁检测消耗大量cpu资源,cpu利用高,每秒执行不了几个事务;

posted @ 2021-02-22 14:46  惊风破浪的博客  阅读(58)  评论(0编辑  收藏  举报