读书人

【转载】InnoDB锁有关问题 -《深入浅出

发布时间: 2012-07-31 12:33:46 作者: rapoo

【转载】InnoDB锁问题 -《深入浅出MySQL——数据库开发、优化与管理维护》

links:

http://blog.sina.com.cn/s/blog_53b13d950100vtks.html

?

核心提示:InnoDB锁问题 InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题。下面我们先介绍一点背景知识,然后详细讨论InnoDB的锁问题。??

InnoDB锁问题

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题。下面我们先介绍一点背景知识,然后详细讨论InnoDB的锁问题。

背景知识

1.事务(Transaction)及其ACID属性

事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。

l????????

读数据一致性及允许的并发副作用

隔离级别

读数据一致性

脏读

不可重复读

幻读

未提交读(Read uncommitted)

最低级别,只能保证不读取物理上损坏的数据

已提交度(Read committed)

语句级

可重复读(Repeatable read)

事务级

可序列化(Serializable)

最高级别,事务级

最后要说明的是:各具体数据库并不一定完全实现了上述4个隔离级别,例如,Oracle只提供Read committed和Serializable两个标准隔离级别,另外还提供自己定义的Read only隔离级别;SQL Server除支持上述ISO/ANSI SQL92定义的4个隔离级别外,还支持一个叫做“快照”的隔离级别,但严格来说它是一个用MVCC实现的Serializable隔离级别。MySQL支持全部4个隔离级别,但在具体实现时,有一些特点,比如在一些隔离级别下是采用MVCC一致性读,但某些情况下又不是,这些内容在后面的章节中将会做进一步介绍。

获取InnoDB行锁争用情况????

可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:

mysql> show status like 'innodb_row_lock%';

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

| Variable_name????????????????

请求锁模式

??

X

IX

S

IS

X

冲突

冲突

冲突

冲突

IX

冲突

兼容

冲突

兼容

S

冲突

冲突

兼容

兼容

IS

冲突

兼容

兼容

兼容

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

??

session_1

session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

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

| actor_id | first_name | last_name |

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

| 178?????

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

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

| actor_id | first_name | last_name |

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

| 178?????

当前session对actor_id=178的记录加share mode 的共享锁:

mysql> select actor_id,first_name,last_name from actor where actor_id = 178 lock in share mode;

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

| actor_id | first_name | last_name |

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

| 178???????

其他session仍然可以查询记录,并也可以对该记录加share mode的共享锁:

mysql> select actor_id,first_name,last_name from actor where actor_id = 178 lock in share mode;

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

| actor_id | first_name | last_name |

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

| 178?????

当前session对锁定的记录进行更新操作,等待锁:

mysql> update actor set last_name = 'MONROE T' where actor_id = 178;

等待

??

其他session也对该记录进行更新操作,则会导致死锁退出:

mysql> update actor set last_name = 'MONROE T' where actor_id = 178;

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

获得锁后,可以成功更新:

mysql> update actor set last_name = 'MONROE T' where actor_id = 178;

Query OK, 1 row affected (17.67 sec)

Rows matched: 1??

???

session_1

session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

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

| actor_id | first_name | last_name |

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

| 178?????

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

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

| actor_id | first_name | last_name |

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

| 178?????

当前session对actor_id=178的记录加for update的共享锁:

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

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

| actor_id | first_name | last_name |

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

| 178???????

其他session可以查询该记录,但是不能对该记录加共享锁,会等待获得锁:

mysql> select actor_id,first_name,last_name from actor where actor_id = 178;

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

| actor_id | first_name | last_name |

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

| 178?????

当前session可以对锁定的记录进行更新操作,更新后释放锁:

mysql> update actor set last_name = 'MONROE T' where actor_id = 178;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1???

其他session获得锁,得到其他session提交的记录:

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

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

| actor_id | first_name | last_name |

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

| 178?????

session_1

session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_no_index where id = 1 ;

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

| id??

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_no_index where id = 2 ;

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

| id??

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

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

| id????

mysql> select * from tab_no_index where id = 2 for update;

等待

在如表20-9所示的例子中,看起来session_1只给一行加了排他锁,但session_2在请求其他行的排他锁时,却出现了锁等待!原因就是在没有索引的情况下,InnoDB只能使用表锁。当我们给其增加一个索引后,InnoDB就只锁定了符合条件的行,如表20-10所示。

创建tab_with_index表,id字段有普通索引:

mysql> create table tab_with_index(id int,name varchar(10)) engine=innodb;

Query OK, 0 rows affected (0.15 sec)

mysql> alter table tab_with_index add index id(id);

Query OK, 4 rows affected (0.24 sec)

Records: 4?

session_1

session_2

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 ;

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

| id??

mysql> set autocommit=0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 2 ;

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

| id??

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

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

| id????

mysql> select * from tab_with_index where id = 2 for update;

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

| id??

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 * from tab_with_index where id = 1 and name = '1' for update;

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

| id????

虽然session_2访问的是和session_1不同的记录,但是因为使用了相同的索引,所以需要等待锁:

mysql> select * from tab_with_index where id = 1 and name = '4' for update;

等待

(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。

在如表20-12所示的例子中,表tab_with_index的id字段有主键索引,name字段有普通索引:

mysql> alter table tab_with_index add index name(name);

Query OK, 5 rows affected (0.23 sec)

Records: 5?

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 * from tab_with_index where id = 1 for update;

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

| id ??| name |

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

| 1?????

Session_2使用name的索引访问记录,因为记录没有被索引,所以可以获得锁:

mysql> select * from tab_with_index where name = '2' for update;

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

| id???

由于访问的记录已经被session_1锁定,所以等待获得锁。:

mysql> select * from tab_with_index where name = '4' for update;

(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。关于MySQL在什么情况下不使用索引的详细讨论,参见本章“索引问题”一节的介绍。

在下面的例子中,检索值的数据类型与索引字段不同,虽然MySQL能够进行数据类型转换,但却不会使用索引,从而导致InnoDB使用表锁。通过用explain检查两条SQL的执行计划,我们可以清楚地看到了这一点。

例子中tab_with_index表的name字段有索引,但是name字段是varchar类型的,如果where条件中不是和varchar类型进行比较,则会对name进行类型转换,而执行的全表扫描。

mysql> alter table tab_no_index add index name(name);

Query OK, 4 rows affected (8.06 sec)

Records: 4?

session_1

session_2

mysql> select @@tx_isolation;

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

| @@tx_isolation?

mysql> select @@tx_isolation;

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

| @@tx_isolation?

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

mysql> select * from emp where empid = 102 for update;

Empty set (0.00 sec)

??

这时,如果其他session插入empid为201的记录(注意:这条记录并不存在),也会出现锁等待:

mysql>insert into emp(empid,...) values(201,...);

阻塞等待

Session_1 执行rollback:

mysql> rollback;

Query OK, 0 rows affected (13.04 sec)

??

由于其他session_1回退后释放了Next-Key锁,当前session可以获得锁并成功插入记录:

mysql>insert into emp(empid,...) values(201,...);

Query OK, 1 row affected (13.35 sec)

恢复和复制的需要,对InnoDB锁机制的影响

MySQL通过BINLOG录执行成功的INSERT、UPDATE、DELETE等更新数据的SQL语句,并由此实现MySQL数据库的恢复和主从复制(可以参见本书“管理篇”的介绍)。MySQL的恢复机制(复制其实就是在Slave Mysql不断做基于BINLOG的恢复)有以下特点。

l?

session_1

session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from target_tab;

Empty set (0.00 sec)

mysql> select * from source_tab where name = '1';

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

| d1 | name | d2 |

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

|?

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from target_tab;

Empty set (0.00 sec)

mysql> select * from source_tab where name = '1';

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

| d1 | name | d2 |

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

|?

mysql> insert into target_tab select d1,name from source_tab where name = '1';

Query OK, 5 rows affected (0.00 sec)

Records: 5???

mysql> update source_tab set name = '1' where name = '8';

等待

commit;

??

返回结果

commit;

在上面的例子中,只是简单地读source_tab表的数据,相当于执行一个普通的SELECT语句,用一致性读就可以了。ORACLE正是这么做的,它通过MVCC技术实现的多版本数据来实现一致性读,不需要给source_tab加任何锁。我们知道InnoDB也实现了多版本数据,对普通的SELECT一致性读,也不需要加任何锁;但这里InnoDB却给source_tab加了共享锁,并没有使用多版本数据一致性读技术!

MySQL为什么要这么做呢?其原因还是为了保证恢复和复制的正确性。因为不加锁的话,如果在上述语句执行过程中,其他事务对source_tab做了更新操作,就可能导致数据恢复的结果错误。为了演示这一点,我们再重复一下前面的例子,不同的是在session_1执行事务前,先将系统变量innodb_locks_unsafe_for_binlog的值设置为“on”(其默认值为off),具体结果如表20-15所示。

表20-15????????

session_1

session_2

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql>set innodb_locks_unsafe_for_binlog='on'

Query OK, 0 rows affected (0.00 sec)

mysql> select * from target_tab;

Empty set (0.00 sec)

mysql> select * from source_tab where name = '1';

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

| d1 | name | d2 |

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

|?

mysql> set autocommit = 0;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from target_tab;

Empty set (0.00 sec)

mysql> select * from source_tab where name = '1';

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

| d1 | name | d2 |

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

|?

mysql> insert into target_tab select d1,name from source_tab where name = '1';

Query OK, 5 rows affected (0.00 sec)

Records: 5???

session_1未提交,可以对session_1的select的记录进行更新操作。

mysql> update source_tab set name = '8' where name = '1';

Query OK, 5 rows affected (0.00 sec)

Rows matched: 5??

更新操作先提交

mysql> commit;

Query OK, 0 rows affected (0.05 sec)

插入操作后提交

mysql> commit;

Query OK, 0 rows affected (0.07 sec)

?

此时查看数据,target_tab中可以插入source_tab更新前的结果,这符合应用逻辑:

mysql> select * from source_tab where name = '8';

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

| d1 | name | d2 |

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

|?

mysql> select * from tt1 where name = '1';

Empty set (0.00 sec)

mysql> select * from source_tab where name = '8';

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

| d1 | name | d2 |

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

|?

隔离级别

???????

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表的理由。但在个别特殊事务中,也可以考虑使用表级锁。

??

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???????

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????

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

Query OK, 1 row affected (0.00 sec)

mysql>???

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

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

| first_name | last_name |

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

| PENELOPE??

mysql>??

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

表20-19???????

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????

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

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

| first_name | last_name |

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

| ED????????

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?????????

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

??????

session_1

session_2

mysql> select @@tx_isolation;

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

| @@tx_isolation?

mysql> select @@tx_isolation;

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

| @@tx_isolation?

当前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???????????????????????

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输出的样例:

mysql> show innodb status \G

…….

------------------------

LATEST DETECTED DEADLOCK

------------------------

070710 14:05:16

*** (1) TRANSACTION:

TRANSACTION 0 117470078, ACTIVE 117 sec, process no 1468, OS thread id 1197328736 inserting

mysql tables in use 1, locked 1

LOCK WAIT 5 lock struct(s), heap size 1216

MySQL thread id 7521657, query id 673468054 localhost root update

insert into country (country_id,country) values(110,'Test')

………

*** (2) TRANSACTION:

TRANSACTION 0 117470079, ACTIVE 39 sec, process no 1468, OS thread id 1164048736 starting index read, thread declared inside InnoDB 500

mysql tables in use 1, locked 1

4 lock struct(s), heap size 1216, undo log entries 1

MySQL thread id 7521664, query id 673468058 localhost root statistics

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

*** (2) HOLDS THE LOCK(S):

………

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

………

*** WE ROLL BACK TRANSACTION (1)

……

读书人网 >其他数据库

热点推荐