MySQL- 锁(3)

InnoDB在不同隔离级别下的一致性读及锁的差异

前面讲过,锁和多版本数据是InnoDB实现一致性读和ISO/ANSI SQL92隔离级别的手段,因此,在不同的隔离级别下,InnoDB处理SQL时采用的一致性读策略和需要的锁是不同的。同时,数据恢复和复制机制的特点,也对一些SQL的一致性读策略和锁策略有很大影响。将这些特性归纳成如表20-16所示的内容,以便读者查阅。

表20-16                                          InnoDB存储引擎中不同SQL在不同隔离级别下锁比较


隔离级别

一致性读和锁

SQL


Read Uncommited


Read Commited


Repeatable Read


Serializable


SQL


条件

       

select


相等


None locks


Consisten read/None lock


Consisten read/None lock


Share locks


范围


None locks


Consisten read/None lock


Consisten read/None lock


Share Next-Key


update


相等


exclusive locks


exclusive locks


exclusive locks


Exclusive locks


范围


exclusive next-key


exclusive next-key


exclusive next-key


exclusive next-key


Insert


N/A


exclusive locks


exclusive locks


exclusive locks


exclusive locks


replace


无键冲突


exclusive locks


exclusive locks


exclusive locks


exclusive locks


键冲突


exclusive next-key


exclusive next-key


exclusive next-key


exclusive next-key


delete


相等


exclusive locks


exclusive locks


exclusive locks


exclusive locks


范围


exclusive next-key


exclusive next-key


exclusive next-key


exclusive next-key


Select ... from ... Lock in share mode


相等


Share locks


Share locks


Share locks


Share locks


范围


Share locks


Share locks


Share Next-Key


Share Next-Key


Select * from ... For update


相等


exclusive locks


exclusive locks


exclusive locks


exclusive locks


范围


exclusive locks


Share locks


exclusive next-key


exclusive next-key


Insert into ... Select ...

(指源表锁)


innodb_locks_unsafe_for_binlog=off


Share Next-Key


Share Next-Key


Share Next-Key


Share Next-Key


innodb_locks_unsafe_for_binlog=on


None locks


Consisten read/None lock


Consisten read/None lock


Share Next-Key


create table ... Select ...

(指源表锁)


innodb_locks_unsafe_for_binlog=off


Share Next-Key


Share Next-Key


Share Next-Key


Share Next-Key


innodb_locks_unsafe_for_binlog=on


None locks


Consisten read/None lock


Consisten read/None lock


Share Next-Key

从表20-16可以看出:对于许多SQL,隔离级别越高,InnoDB给记录集加的锁就越严格(尤其是使用范围条件的时候),产生锁冲突的可能性也就越高,从而对并发性事务处理性能的影响也就越大。因此,我们在应用中,应该尽量使用较低的隔离级别,以减少锁争用的机率。实际上,通过优化事务逻辑,大部分应用使用Read Commited隔离级别就足够了。对于一些确实需要更高隔离级别的事务,可以通过在程序中执行SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ或SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE动态改变隔离级别的方式满足需求。

什么时候使用表锁

对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁。

¡  第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。

¡  第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。

当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表了。

在InnoDB下,使用表锁要注意以下两点。

(1)使用LOCK TABLES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层──MySQL Server负责的,仅当autocommit=0、innodb_table_locks=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁;否则,InnoDB将无法自动检测并处理这种死锁。有关死锁,下一小节还会继续讨论。

(2)在用 LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。正确的方式见如下语句:

例如,如果需要写表t1并从表t读,可以按如下做:


1

2

3

4

5

关于死锁

上文讲过,MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了在InnoDB中发生死锁是可能的。如表20-17所示的就是一个发生死锁的例子。

表20-17 InnoDB存储引擎中的死锁例子


session_1


session_2


mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from table_1 where where id=1 for update;

...

做一些其他处理...


mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from table_2 where id=1 for update;

...


select * from table_2 where id =1 for update;

因session_2已取得排他锁,等待


做一些其他处理...

 
mysql> select * from table_1 where where id=1 for update;

死锁

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

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

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

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

表20-18        InnoDB存储引擎中表顺序造成的死锁例子


session_1


session_2


mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)


mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)


mysql> select first_name,last_name from actor where actor_id = 1 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| PENELOPE   | GUINESS   |

+------------+-----------+

1 row in set (0.00 sec)

 
 
mysql> insert into country (country_id,country) values(110,‘Test‘);

Query OK, 1 row affected (0.00 sec)


mysql>  insert into country (country_id,country) values(110,‘Test‘);

等待

 
 
mysql> select first_name,last_name from actor where actor_id = 1 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| PENELOPE   | GUINESS   |

+------------+-----------+

1 row in set (0.00 sec)


mysql>  insert into country (country_id,country) values(110,‘Test‘);

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 

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

表20-19        InnoDB存储引擎中表数据操作顺序不一致造成的死锁例子


session_1


session_2


mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)


mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)


mysql> select first_name,last_name from actor where actor_id = 1 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| PENELOPE   | GUINESS   |

+------------+-----------+

1 row in set (0.00 sec)

 
 
mysql> select first_name,last_name from actor where actor_id = 3 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| ED         | CHASE     |

+------------+-----------+

1 row in set (0.00 sec)


mysql> select first_name,last_name from actor where actor_id = 3 for update;

等待

 
 
mysql> select first_name,last_name from actor where actor_id = 1 for update;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction


mysql> select first_name,last_name from actor where actor_id = 3 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| ED         | CHASE     |

+------------+-----------+

1 row in set (4.71 sec)

 

(3)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。具体演示可参见20.3.3小节中的例子。

(4)前面讲过,在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT...FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题,如表20-20所示。

表20-20   InnoDB存储引擎中隔离级别引起的死锁例子1


session_1


session_2


mysql> select @@tx_isolation;

+-----------------+

| @@tx_isolation  |

+-----------------+

| REPEATABLE-READ |

+-----------------+

1 row in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)


mysql> select @@tx_isolation;

+-----------------+

| @@tx_isolation  |

+-----------------+

| REPEATABLE-READ |

+-----------------+

1 row in set (0.00 sec)

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)


当前session对不存在的记录加for update的锁:

mysql> select actor_id,first_name,last_name from actor where actor_id = 201 for update;

Empty set (0.00 sec)

 
 
其他session也可以对不存在的记录加for update的锁:

mysql> select actor_id,first_name,last_name from actor where actor_id = 201 for update;

Empty set (0.00 sec)


因为其他session也对该记录加了锁,所以当前的插入会等待:

mysql> insert into actor (actor_id , first_name , last_name) values(201,‘Lisa‘,‘Tom‘);

等待

 
 
因为其他session已经对记录进行了更新,这时候再插入记录就会提示死锁并退出:

mysql> insert into actor (actor_id, first_name , last_name) values(201,‘Lisa‘,‘Tom‘);

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction


由于其他session已经退出,当前session可以获得锁并成功插入记录:

mysql> insert into actor (actor_id , first_name , last_name) values(201,‘Lisa‘,‘Tom‘);

Query OK, 1 row affected (13.35 sec)

 

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

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

表20-21   InnoDB存储引擎中隔离级别引起的死锁例子2


session_1


session_2


session_3


mysql> select @@tx_isolation;

+----------------+

| @@tx_isolation |

+----------------+

| READ-COMMITTED |

+----------------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.01 sec)


mysql> select @@tx_isolation;

+----------------+

| @@tx_isolation |

+----------------+

| READ-COMMITTED |

+----------------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.01 sec)


mysql> select @@tx_isolation;

+----------------+

| @@tx_isolation |

+----------------+

| READ-COMMITTED |

+----------------+

1 row in set (0.00 sec)

mysql> set autocommit=0;

Query OK, 0 rows affected (0.01 sec)


Session_1获得for update的共享锁:

mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update;

Empty set (0.00 sec)


由于记录不存在,session_2也可以获得for update的共享锁:

mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update;

Empty set (0.00 sec)

 

Session_1可以成功插入记录:

mysql> insert into actor (actor_id,first_name,last_name) values(201,‘Lisa‘,‘Tom‘);

Query OK, 1 row affected (0.00 sec)

   
 
Session_2插入申请等待获得锁:

mysql> insert into actor (actor_id,first_name,last_name) values(201,‘Lisa‘,‘Tom‘);

等待

 

Session_1成功提交:

mysql> commit;

Query OK, 0 rows affected (0.04 sec)

   
 
Session_2获得锁,发现插入记录主键重,这个时候抛出了异常,但是并没有释放共享锁:

mysql> insert into actor (actor_id,first_name,last_name) values(201,‘Lisa‘,‘Tom‘);

ERROR 1062 (23000): Duplicate entry ‘201‘ for key ‘PRIMARY‘

 
   
Session_3申请获得共享锁,因为session_2已经锁定该记录,所以session_3需要等待:

mysql> select actor_id, first_name,last_name from actor where actor_id = 201 for update;

等待

 
这个时候,如果session_2直接对记录进行更新操作,则会抛出死锁的异常:

mysql> update actor set last_name=‘Lan‘ where actor_id = 201;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 
   
Session_2释放锁后,session_3获得锁:

mysql> select first_name, last_name from actor where actor_id = 201 for update;

+------------+-----------+

| first_name | last_name |

+------------+-----------+

| Lisa       | Tom       |

+------------+-----------+

1 row in set (31.12 sec)

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

如果出现死锁,可以用SHOW INNODB STATUS命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的SQL语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。下面是一段SHOW INNODB STATUS输出的样例:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

小结

本章重点介绍了MySQL中MyISAM表级锁和InnoDB行级锁的实现特点,并讨论了两种存储引擎经常遇到的锁问题和解决办法。

对于MyISAM的表锁,主要讨论了以下几点:

(1)共享读锁(S)之间是兼容的,但共享读锁(S)与排他写锁(X)之间,以及排他写锁(X)之间是互斥的,也就是说读和写是串行的。

(2)在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表查询和插入的锁争用问题。

(3)MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。

(4)由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新操作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突。

对于InnoDB表,本章主要讨论了以下几项内容。

l         InnoDB的行锁是基于锁引实现的,如果不通过索引访问数据,InnoDB会使用表锁。

l         介绍了InnoDB间隙锁(Next-key)机制,以及InnoDB使用间隙锁的原因。

l         在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。

l         MySQL的恢复和复制对InnoDB锁机制和一致性读策略也有较大影响。

l         锁冲突甚至死锁很难完全避免。

在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:

l         尽量使用较低的隔离级别;

l         精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;

l         选择合理的事务大小,小事务发生锁冲突的几率也更小;

l         给记录集显示加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;

l         不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;

l         尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响;

l         不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;

l         对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能

时间: 2024-10-02 07:00:34

MySQL- 锁(3)的相关文章

mysql锁

锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的计算资源(如CPU.RAM.I/O等)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素.从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂.本章我们着重讨论MySQL锁机制的特点,常见的锁问题,以及解决MySQL锁问题的一些方法或建议. MySQL锁概述 相对其他数据库而言,MySQL的锁机制比较简单,其最显著

MySQL锁与MVCC

--MySQL锁与MVCC --------------------2014/06/29 myisam表锁比较简单,这里主要讨论一下innodb的锁相关问题. innodb相比oracle锁机制简单许多,锁的类型有如下几类: A shared (S) lock permits the transaction that holds the lock to read a row. An exclusive (X) lock permits the transaction that holds the

PHP.37-扩展-锁机制解决并发-MySQL锁、PHP文件锁

锁机制适用于高并发场景:高并发订单.秒杀-- apache压力测试 Mysql锁详解 语法 加锁:LOCK TABLE 表名1 READ|WRITE, 表名2 READ|WRITE .................. 解锁:UNLOCK TABLES Read:读锁|共享锁 : 所有的客户端只能读这个表不能写这个表 Write:写锁|排它锁: 所有当前锁定客户端可以操作这个表,其他客户端只能阻塞 注意:在锁表的过程中只能操作被锁定的表,如果要操作其他表,必须把所有要操作的表都锁定起来!! PH

MySQL锁解决并发问题详解

文章分为以下几个要点 问题描述以及解决过程 MySQL锁机制 数据库加锁分析 下面讨论的都是基于MySQL的InnoDB. 0. 问题描述以及解决过程 因为涉及到公司利益问题,所以下面很多代码和数据库信息,进行了缩减和修改,望见谅. 业务场景是优惠券系统规则规定了一个优惠券活动最多可发行多少张优惠券和每个用户最多可领取优惠券数量. 下面列出两张表的结构. 活动表 CREATE TABLE `coupon_activity` ( `act_id` int(11) NOT NULL AUTO_INC

MySQL锁和隔离机制

一:MySQL锁 二:MySQL事务 三:MySQL隔离级别 1.1为什么要有锁? 因为数据库要解决并发控制问题.在同一时刻,可能会有多个客户端对某张表的某条数据或某些数据进行操作,比如有的在读取该行数据,其他的尝试去删除它.为了保证数据的一致性,数据库就要对这种并发操作进行控制,因此就有了锁的概念. 1.2锁的分类     1.2.1按对数据的操作类型         LOCK TABLES tb_namelock_type{READ|WRITE};         UNLOCKTABLES

mysql 锁详解

锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的计算资源(如CPU.RAM.I/O等)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素. MySQL有3种锁机制,特性可大致归纳如下. ·表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. ·行级锁:开销大,加锁慢:会出现死锁:锁定粒度最小,发生锁冲突的概率最低,并发度也最高. ·

mysql锁机制(转载)

锁是计算机协调多个进程或线程并发访问某一资源的机制 .在数据库中,除传统的 计算资源(如CPU.RAM.I/O等)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所有数据库必须解决的一 个问题,锁冲突也是影响数据库并发访问性能的一个重要因素. 从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂.本章我们着重讨论MySQL锁机制 的特点,常见的锁问题,以及解决MySQL锁问题的一些方法或建议. MySQL锁概述相对其他数据库而言,MySQL的锁机制比较简单,

【MySQL】MySQL锁和隔离级别浅析一

参考:http://imysql.cn/2008_07_10_innodb_tx_isolation_and_lock_mode 本文只是对于"SELECT ... LOCK IN SHARE MODE"和"SELECT ... FORUPDATE"事务中的锁和RR隔离级别内的测试,针对于表结构.索引结构以及其他隔离级别情况下的触发锁类型,可以参考网易何登成网盘中"MySQL 加锁处理分析.pdf"这篇文章,很细致. 何登成百度网盘:http:/

mysql锁监视器

根据MySQL锁监视器这篇文章来操作. 准备条件:大于5.6.16版本的MySQL,登录mysql 开启 set GLOBAL innodb_status_output=ON; set GLOBAL innodb_status_output_locks=ON; 新增.修改表. mysql> show create table my_test1_0\G *************************** 1. row *************************** Table: my_

对mysql锁机制的学习

1.对于mysql学习,经常翻看一些博客,论坛,好像或多或少有mysq锁机制的学习与总结,所以今天有必要 对mysql锁机制的一些个人的总结,以便以后深入的学习. 2.学习这件事,从来都是“深入浅出”的,今天留个痕迹,说不定以后“受益匪浅”. a.数据库锁是什么 数据库锁就是为了保证数据库数据的一致性在一个共享资源被并发访问时使得数据访问顺序化的机制. MySQL数据库的锁机制比较独特,支持不同的存储引擎使用不同的锁机制. b.mysql锁机制的3中类型 表级锁,行级锁,页级锁 c.各级锁的特点