大家好,又见面了,我是你们的朋友全栈君。
查询表级锁争用情况
show status like ‘table%’;
MySQL表级锁的锁模式
请求锁模式 是否兼容 当前锁模式 |
None |
读锁 |
写锁 |
读锁 |
是 |
是 |
否 |
写锁 |
是 |
否 |
否 |
session_1 |
session_2 |
获得表film_text的WRITE锁定 mysql> lock table film_text write; Query OK, 0 rows affected (0.00 sec) |
|
当前session对锁定表的查询、更新、插入操作都可以执行: mysql> select film_id,title from film_text where film_id = 1001; +———+————-+ | film_id | title | +———+————-+ | 1001 | Update Test | +———+————-+ 1 row in set (0.00 sec) mysql> insert into film_text (film_id,title) values(1003,’Test’); Query OK, 1 row affected (0.00 sec) mysql> update film_text set title = ‘Test’ where film_id = 1001; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
其他session对锁定表的查询被阻塞,需要等待锁被释放: mysql> select film_id,title from film_text where film_id = 1001; 等待 |
释放锁: mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) |
等待 |
Session2获得锁,查询返回: mysql> select film_id,title from film_text where film_id = 1001; +———+——-+ | film_id | title | +———+——-+ | 1001 | Test | +———+——-+ 1 row in set (57.59 sec) |
如何加表锁
在用LOCK TABLES给表显式加表锁时,必须同时取得所有涉及到表的锁,并且MySQL不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,
在自动加锁的情况下也基本如此,MyISAM总是一次获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的原因。
session_1 |
session_2 |
获得表film_text的READ锁定 mysql> lock table film_text read; Query OK, 0 rows affected (0.00 sec) |
|
当前session可以查询该表记录 mysql> select film_id,title from film_text where film_id = 1001; +———+——————+ | film_id | title | +———+——————+ | 1001 | ACADEMY DINOSAUR | +———+——————+ 1 row in set (0.00 sec) |
其他session也可以查询该表的记录 mysql> select film_id,title from film_text where film_id = 1001; +———+——————+ | film_id | title | +———+——————+ | 1001 | ACADEMY DINOSAUR | +———+——————+ 1 row in set (0.00 sec) |
当前session不能查询没有锁定的表 mysql> select film_id,title from film where film_id = 1001; ERROR 1100 (HY000): Table ‘film’ was not locked with LOCK TABLES |
其他session可以查询或者更新未锁定的表 mysql> select film_id,title from film where film_id = 1001; +———+—————+ | film_id | title | +———+—————+ | 1001 | update record | +———+—————+ 1 row in set (0.00 sec) mysql> update film set title = ‘Test’ where film_id = 1001; Query OK, 1 row affected (0.04 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
当前session中插入或者更新锁定的表都会提示错误: mysql> insert into film_text (film_id,title) values(1002,’Test’); ERROR 1099 (HY000): Table ‘film_text’ was locked with a READ lock and can’t be updated mysql> update film_text set title = ‘Test’ where film_id = 1001; ERROR 1099 (HY000): Table ‘film_text’ was locked with a READ lock and can’t be updated |
其他session更新锁定表会等待获得锁: mysql> update film_text set title = ‘Test’ where film_id = 1001; 等待 |
释放锁 mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) |
等待 |
Session获得锁,更新操作完成: mysql> update film_text set title = ‘Test’ where film_id = 1001; Query OK, 1 row affected (1 min 0.71 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
并发插入(Concurrent Inserts)
session_1 |
session_2 |
获得表film_text的READ LOCAL锁定 mysql> lock table film_text read local; Query OK, 0 rows affected (0.00 sec) |
|
当前session不能对锁定表进行更新或者插入操作: mysql> insert into film_text (film_id,title) values(1002,’Test’); ERROR 1099 (HY000): Table ‘film_text’ was locked with a READ lock and can’t be updated mysql> update film_text set title = ‘Test’ where film_id = 1001; ERROR 1099 (HY000): Table ‘film_text’ was locked with a READ lock and can’t be updated |
其他session可以进行插入操作,但是更新会等待: mysql> insert into film_text (film_id,title) values(1002,’Test’); Query OK, 1 row affected (0.00 sec) mysql> update film_text set title = ‘Update Test’ where film_id = 1001; 等待 |
当前session不能访问其他session插入的记录: mysql> select film_id,title from film_text where film_id = 1002; Empty set (0.00 sec) |
|
释放锁: mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) |
等待 |
当前session解锁后可以获得其他session插入的记录: mysql> select film_id,title from film_text where film_id = 1002; +———+——-+ | film_id | title | +———+——-+ | 1002 | Test | +———+——-+ 1 row in set (0.00 sec) |
Session2获得锁,更新操作完成: mysql> update film_text set title = ‘Update Test’ where film_id = 1001; Query OK, 1 row affected (1 min 17.75 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
通过定期在系统空闲时段执行 OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。有关OPTIMIZE TABLE语句的详细介绍,可以参见第18章中“两个简单实用的优化方法”一节的内容。
MyISAM的锁调度
写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前!这是因为MySQL认为写请求一般比读请求要重要。
这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!幸好我们可以通过一些设置来调节MyISAM 的调度行为。
max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。
这里还要强调一点:一些需要长时间运行的查询操作,也会使写进程“饿死”!因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语句来解决问题,因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。
背景知识
1.事务(Transaction)及其ACID属性
2.并发事务处理带来的问题
3.事务隔离级别
,“更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。
读数据一致性及允许的并发副作用 隔离级别 |
读数据一致性 |
脏读 |
不可重复读 |
幻读 |
未提交读(Read uncommitted) |
最低级别,只能保证不读取物理上损坏的数据 |
是 |
是 |
是 |
已提交度(Read committed) |
语句级 |
否 |
是 |
是 |
可重复读(Repeatable read) |
事务级 |
否 |
否 |
是 |
可序列化(Serializable) |
最高级别,事务级 |
否 |
否 |
否 |
获取InnoDB行锁争用情况
show status like ‘innodb_row_lock%’;
InnoDB的行锁模式及加锁方法
请求锁模式 是否兼容 当前锁模式 |
X |
IX |
S |
IS |
X |
冲突 |
冲突 |
冲突 |
冲突 |
IX |
冲突 |
兼容 |
冲突 |
兼容 |
S |
冲突 |
冲突 |
兼容 |
兼容 |
IS |
冲突 |
兼容 |
兼容 |
兼容 |
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 | LISA | MONROE | +———-+————+———–+ 1 row in set (0.00 sec) |
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 | LISA | MONROE | +———-+————+———–+ 1 row in set (0.00 sec) |
当前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 | LISA | MONROE | +———-+————+———–+ 1 row in set (0.01 sec) |
|
其他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 | LISA | MONROE | +———-+————+———–+ 1 row in set (0.01 sec) |
|
当前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 Changed: 1 Warnings: 0 |
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 | LISA | MONROE | +———-+————+———–+ 1 row in set (0.00 sec) |
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 | LISA | MONROE | +———-+————+———–+ 1 row in set (0.00 sec) |
当前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 | LISA | MONROE | +———-+————+———–+ 1 row in set (0.00 sec) |
|
其他session可以查询该记录,但是不能对该记录加共享锁,会等待获得锁: mysql> select actor_id,first_name,last_name from actor where actor_id = 178; +———-+————+———–+ | actor_id | first_name | last_name | +———-+————+———–+ | 178 | LISA | MONROE | +———-+————+———–+ 1 row in set (0.00 sec) mysql> select actor_id,first_name,last_name from actor where actor_id = 178 for update; 等待 |
|
当前session可以对锁定的记录进行更新操作,更新后释放锁: mysql> update actor set last_name = ‘MONROE T’ where actor_id = 178; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.01 sec) |
|
其他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 | LISA | MONROE T | +———-+————+———–+ 1 row in set (9.59 sec) |
InnoDB行锁实现方式
通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!
InnoDB存储引擎的表在不使用索引时使用表锁例子
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 | name | +——+——+ | 1 | 1 | +——+——+ 1 row in set (0.00 sec) |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) mysql> select * from tab_no_index where id = 2 ; +——+——+ | id | name | +——+——+ | 2 | 2 | +——+——+ 1 row in set (0.00 sec) |
mysql> select * from tab_no_index where id = 1 for update; +——+——+ | id | name | +——+——+ | 1 | 1 | +——+——+ 1 row in set (0.00 sec) |
|
mysql> select * from tab_no_index where id = 2 for update; 等待 |
InnoDB存储引擎的表在使用索引时使用行锁例子
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 | name | +——+——+ | 1 | 1 | +——+——+ 1 row in set (0.00 sec) |
mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) mysql> select * from tab_with_index where id = 2 ; +——+——+ | id | name | +——+——+ | 2 | 2 | +——+——+ 1 row in set (0.00 sec) |
mysql> select * from tab_with_index where id = 1 for update; +——+——+ | id | name | +——+——+ | 1 | 1 | +——+——+ 1 row in set (0.00 sec) |
|
mysql> select * from tab_with_index where id = 2 for update; +——+——+ | id | name | +——+——+ | 2 | 2 | +——+——+ 1 row in set (0.00 sec) |
由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。
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 * from tab_with_index where id = 1 and name = ‘1’ for update; +——+——+ | id | name | +——+——+ | 1 | 1 | +——+——+ 1 row in set (0.00 sec) |
|
虽然session_2访问的是和session_1不同的记录,但是因为使用了相同的索引,所以需要等待锁: mysql> select * from tab_with_index where id = 1 and name = ‘4’ for update; 等待 |
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 * from tab_with_index where id = 1 for update; +——+——+ | id | name | +——+——+ | 1 | 1 | | 1 | 4 | +——+——+ 2 rows in set (0.00 sec) |
|
Session_2使用name的索引访问记录,因为记录没有被索引,所以可以获得锁: mysql> select * from tab_with_index where name = ‘2’ for update; +——+——+ | id | name | +——+——+ | 2 | 2 | +——+——+ 1 row in set (0.00 sec) |
|
由于访问的记录已经被session_1锁定,所以等待获得锁。: mysql> select * from tab_with_index where name = ‘4’ for update; |
因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。关于MySQL在什么情况下不使用索引的详细讨论,参见本章“索引问题”一节的介绍。
计划,我们可以清楚地看到了这一点。
间隙锁(Next-Key锁)
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 * from emp where empid = 102 for update; Empty set (0.00 sec) |
|
这时,如果其他session插入empid为102的记录(注意:这条记录并不存在),也会出现锁等待: mysql>insert into emp(empid,…) values(102,…); 阻塞等待 |
|
Session_1 执行rollback: mysql> rollback; Query OK, 0 rows affected (13.04 sec) |
|
由于其他session_1回退后释放了Next-Key锁,当前session可以获得锁并成功插入记录: mysql>insert into emp(empid,…) values(102,…); Query OK, 1 row affected (13.35 sec) |
恢复和复制的需要,对InnoDB锁机制的影响
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 | +—-+——+—-+ | 4 | 1 | 1 | | 5 | 1 | 1 | | 6 | 1 | 1 | | 7 | 1 | 1 | | 8 | 1 | 1 | +—-+——+—-+ 5 rows in set (0.00 sec) |
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 | +—-+——+—-+ | 4 | 1 | 1 | | 5 | 1 | 1 | | 6 | 1 | 1 | | 7 | 1 | 1 | | 8 | 1 | 1 | +—-+——+—-+ 5 rows in set (0.00 sec) |
mysql> insert into target_tab select d1,name from source_tab where name = ‘1’; Query OK, 5 rows affected (0.00 sec) Records: 5 Duplicates: 0 Warnings: 0 |
|
mysql> update source_tab set name = ‘1’ where name = ‘8’; 等待 |
|
commit; |
|
返回结果 commit; |
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 | +—-+——+—-+ | 4 | 1 | 1 | | 5 | 1 | 1 | | 6 | 1 | 1 | | 7 | 1 | 1 | | 8 | 1 | 1 | +—-+——+—-+ 5 rows in set (0.00 sec) |
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 | +—-+——+—-+ | 4 | 1 | 1 | | 5 | 1 | 1 | | 6 | 1 | 1 | | 7 | 1 | 1 | | 8 | 1 | 1 | +—-+——+—-+ 5 rows in set (0.00 sec) |
mysql> insert into target_tab select d1,name from source_tab where name = ‘1’; Query OK, 5 rows affected (0.00 sec) Records: 5 Duplicates: 0 Warnings: 0 |
|
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 Changed: 5 Warnings: 0 mysql> select * from source_tab where name = ‘8’; +—-+——+—-+ | d1 | name | d2 | +—-+——+—-+ | 4 | 8 | 1 | | 5 | 8 | 1 | | 6 | 8 | 1 | | 7 | 8 | 1 | | 8 | 8 | 1 | +—-+——+—-+ 5 rows in set (0.00 sec) |
|
更新操作先提交 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 | +—-+——+—-+ | 4 | 8 | 1 | | 5 | 8 | 1 | | 6 | 8 | 1 | | 7 | 8 | 1 | | 8 | 8 | 1 | +—-+——+—-+ 5 rows in set (0.00 sec) mysql> select * from target_tab; +——+——+ | id | name | +——+——+ | 4 | 1.00 | | 5 | 1.00 | | 6 | 1.00 | | 7 | 1.00 | | 8 | 1.00 | +——+——+ 5 rows in set (0.00 sec) |
mysql> select * from tt1 where name = ‘1’; Empty set (0.00 sec) mysql> select * from source_tab where name = ‘8’; +—-+——+—-+ | d1 | name | d2 | +—-+——+—-+ | 4 | 8 | 1 | | 5 | 8 | 1 | | 6 | 8 | 1 | | 7 | 8 | 1 | | 8 | 8 | 1 | +—-+——+—-+ 5 rows in set (0.00 sec) mysql> select * from target_tab; +——+——+ | id | name | +——+——+ | 4 | 1.00 | | 5 | 1.00 | | 6 | 1.00 | | 7 | 1.00 | | 8 | 1.00 | +——+——+ 5 rows in set (0.00 sec) |
InnoDB在不同隔离级别下的一致性读及锁的差异
隔离级别 一致性读和锁 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 |
什么时候使用表锁
关于死锁
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; 死锁 |
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 |
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) |
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) |
当隔离级别为READ COMMITTED时,如果两个线程都先执行SELECT…FOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会获得一个排他锁!这时如果有第3个线程又来申请排他锁,也会出现死锁。
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) |
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/134659.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...