MySQL——InnoDB锁问题(六)

时间:2022-03-04 07:32:46

一、在InnoDB情况下什么时候使用表锁。

对于InnoDB表,大多数情况都应该使用行锁,因为事务和行锁往往是我们选择InnoDB表的重要原因。但在特殊的情况下,也可以使用表级锁。

(1)、事务需要更新大部分数据或者全部数据,表又比较大,如果使用默认的行级锁,不仅使得事务执行比较慢效率低,而且可能造成其他事务长时间锁等待和锁冲突,这样的情况可以考虑使用表级锁来提高执行效率。

(2)、事务涉及多个表,又比较复杂,很可能引起死锁,造成大量事务回滚,可以考虑一次性获取事务涉及的所有表,从而避免死锁,减少数据库因事务回滚产生的开销。


当然了,应用中这样的情况不能太多,否则可以考虑换成MyISAM表了,在InnoDB下使用表级锁应该注意以下两点。

MySQL——InnoDB锁问题(六)

、死锁问题。

对于MyISAM表是不会产生死锁的,因为表锁总是一次性获取所有SQL语句涉及到的所有表级锁。但是在InnoDB表中,除了单个SQL语句组成的事务外,不然锁都是逐步获取的。这就让InnoDB表产生死锁的可能。如下是发生死锁的栗子。

MySQL——InnoDB锁问题(六)

在上面的例子中,两个事务都需要获得对方占有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。

发生死锁后,一般情况下InnoDB都能够自动检测到,使一个事务释放锁并退回。另一个事务获得锁,继续完成事务。但是在设计外部锁或者表锁时,InnoDB无法检查到死锁情况,这时需要设置锁等待超时参数,innodb_lock_wait_timeout来解决。需要说明的是,这个参数不只是用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法获得所需的锁而挂起,会占用计算机大量的资源,造成严重性能问题,甚至拖垮数据库。通过设置合适的锁等待超时阀值,可以有效避免这样的情况发生。

通常来说,死锁都是应用设计的问题,通过调整业务流程,数据库对象设计,事务大小,以及访问数据库SQL语句,绝大部分死锁都可以避免。下面通过实例介绍几种避免死锁的常用方法。

(1)、在应用中,如果不同的程序会并发存取多个表,应尽量以相同的顺序来访问表,这样可以大大降低产生死锁的机会,在下面的例子中,两个session访问两个表的顺序不同,发生死锁机会比较高,但是以相同的顺序来访问,死锁就能避免。

MySQL——InnoDB锁问题(六)

MySQL——InnoDB锁问题(六)

2)、在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按照固定的顺序来处理记录,也大大降低出现死锁额可能。

MySQL——InnoDB锁问题(六)

MySQL——InnoDB锁问题(六)

3)、在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁。而不应该先申请共享锁,更新的时候再申请排他锁。因为当用户申请排他锁时,其他事务可能又获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。

(4)、在前面的情况中,在REPEATABLE-READ隔离级别下,如果两个线程对相同条件记录用SELECT .... FOR UPDATE 加排他锁,在没有符合记录的条件下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新纪录。如果两个线程都折这么做,就会造成死锁。这种情况下,将隔离级别改为READ COMMITED,就可以避免问题。

MySQL——InnoDB锁问题(六)

MySQL——InnoDB锁问题(六)

5)、当隔离级别为READ COMMITED 时,如果两个线程都先执行 SELECT  ..... FOR UPDATE ,判断是否存在符合条件的记录,如果没有,就插入记录,此时,只有一个线程能够插入成功,另一个线程会出现锁等待,当第一个线程提交后,第二个线程会因主键重出错。但虽然这个线程出错了,却会获得一个排他锁,这时如果第三个线程进来申请排他锁,也会出现死锁。

对这样的情况,可以直接做插入操作,然后再捕获主键重异常,或者遇到主键重错误时,总是执行ROLLBACK释放获得的排他锁。

MySQL——InnoDB锁问题(六)

MySQL——InnoDB锁问题(六)

MySQL——InnoDB锁问题(六)

尽管通过上面的设计和SQL优化等措施,可以减少死锁,但死锁很难完全避免,因此,在程序设计中,总是捕获并处理死锁异常是一个很好的编程习惯。

如果出现死锁,可以使用show  innodb status 命令来查看最后一个产生死锁的原因。返回结果中包含死锁相关事务的详细信息。如引发死锁的的SQL语句,事务已经获取锁,正在等待什么锁,以及被回滚的事务等。

MySQL——InnoDB锁问题(六)