为什么要避免长事务

1,682 阅读4分钟

上次讲了因为回滚日志的原因,要避免长事务,否则回滚日志会一直保存。那么还有一些其他原因。

MDL(metadata lock)

MDL不需要显式使用,在访问一个表的时候会被 自动加上。MDL的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个 表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果 跟表结构对不上,肯定是不行的。 因此,在MySQL 5.5版本中引入了MDL,当对一个表做增删改查操作的时候,加MDL读锁;当 要对表做结构变更操作的时候,加MDL写锁。

  • 读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查。
  • 读写锁之间、写锁之间是互斥的,用来保证变更表结构操作的安全性。因此,如果有两个线 程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行

image.png

我们可以看到session A先启动,这时候会对表t加一个MDL读锁。由于session B需要的也是 MDL读锁,因此可以正常执行。
之后session C会被blocked,是因为session A的MDL读锁还没有释放,而session C需要MDL写 锁,因此只能被阻塞。
如果只有session C自己被阻塞还没什么关系,但是之后所有要在表t上新申请MDL读锁的请求也 会被session C阻塞。前面我们说了,所有对表的增删改查操作都需要先申请MDL读锁,就都被锁住,等于这个表现在完全不可读写了。
如果某个表上的查询语句频繁,而且客户端有重试机制,也就是说超时后会再起一个新session 再请求的话,这个库的线程很快就会爆满。 你现在应该知道了,事务中的MDL锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放。

上面这个例子就是说明尽量避免锁住太多的数据(上面直接锁住整张表了)

行锁

image.png 事务B的update语句会被阻塞,直到事务A执行commit之后,事务B才 能继续执行。

在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。让它锁住的时间尽量少。

长事务产生的原因

  1. 操作太多数据
  2. 大量的非db操作,如rpc调用,消息队列的调用。

长事务的影响

  1. 锁住太多的资源,造成大量阻塞,高并发情况下容易造成数据库连接池撑爆
  2. undo log 日志过大

解决长事务方法

  1. 查询操作可以考虑不放到事务中
  2. 避免一次操作过多的数据
  3. 非db操作尽量放到事务之外,如消息队列的操作
  4. 在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

比如: 从顾客A账户余额中扣除电影票价; 2. 给影院B的账户余额增加这张电影票价; 3. 记录一条交易日志。 上面这个事务操作时,会锁住顾客A账户余额的记录,也会锁住影院B余额的账户。很显然第二步操作是影响最大的,也是最容易阻塞住其他事务的,比如顾客B想在影院B买张票,第二步会有冲突,可以按照312的顺序执行,那么它锁住的时间是最少的,提高并发度.