Mysql笔记-锁

这篇文章整理了mysql数据库中有关锁知识的笔记。参考资料《Mysql技术手册》、《MySQL技术内幕InnoDB存储引擎》和《高性能MySQL》。

在接下来的内容中,我们首先接受了表级锁和行级锁,以及死锁问题并举例说明。文章最后,说明了锁带来的问题(脏读、不可重复读、更新覆盖和幻读)。

锁机制用于管理对共享资源的并发访问。

在处理并发读或写时,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为共享锁和排他锁,也叫读锁和写锁。

读锁是共享的,或者说是想不阻塞的。写锁则是排他的,也就是说一个写锁会阻塞其他写锁和读锁。

锁粒度

一种提高共享资源并发性的方式就是让锁定对象更具有选择性。尽量只锁定需要修改的部分,而不是所有的资源。任何时候,在给定资源上,锁定的数量越少,则系统的并发程度越高。

MyISAM和MEMORY存储引擎采用的是表级锁(table-levellocking);BDB存储引擎采用的是页面锁(page-levellocking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

MySQL这3种锁的特性可大致归纳如下。

开销、加锁速度、死锁、粒度、并发性能

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

表级锁

MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。锁模式的兼容性如表所示。

请求锁模式是否兼容当前锁模式

None

读锁

写锁

读锁

写锁

可见,对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;MyISAM表的读操作与写操作之间,以及写操作之间是串行的!

具体语法如下:

LOCK TABLES
    tbl_name[AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
    [, tbl_name[AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ...
UNLOCK TABLES

当您使用LOCK TABLES时,您必须锁定您打算在查询中使用的所有的表。虽然使用LOCK TABLES语句获得的锁定仍然有效,但是您不能访问没有被此语句锁定的任何的表。同时,您不能在一次查询中多次使用一个已锁定的表——使用别名代替,在此情况下,您必须分别获得对每个别名的锁定。

mysql> LOCK TABLE t WRITE, t AS t1 WRITE;
mysql> INSERT INTO t SELECT * FROM t;
ERROR 1100: Table 't' was notlocked with LOCK TABLES
mysql> INSERT INTO t SELECT * FROM t AS t1;

如果您的查询使用一个别名引用一个表,那么您必须使用同样的别名锁定该表。如果没有指定别名,则不会锁定该表。

mysql> LOCK TABLE t READ;
mysql> SELECT * FROM t AS myalias;
ERROR 1100: Table 'myalias' wasnot locked with LOCK TABLES

相反的,如果您使用一个别名锁定一个表,您必须使用该别名在您的查询中引用该表。

mysql> LOCK TABLE t AS myalias READ;
mysql> SELECT * FROM t;
ERROR 1100: Table 't' was not lockedwith LOCK TABLES
mysql> SELECT * FROM t AS myalias;

注意:在使用表锁时,commit和rollback释放不了表锁。必须使用unlock table。

InnoDB的行锁及加锁方式

InnoDB实现了以下两种类型的行锁。

共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。

排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

请求锁模式是否兼容当前锁模式

X

IX

S

IS

X

冲突

冲突

冲突

冲突

IX

冲突

兼容

冲突

兼容

S

冲突

冲突

兼容

兼容

IS

冲突

兼容

兼容

兼容

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

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

¡  共享锁(S):SELECT *FROM table_name WHERE ... LOCK IN SHARE MODE。

¡  排他锁(X):SELECT *FROM table_name WHERE ... FOR UPDATE。

用SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。

InnoDB行锁实现的方式

InnoDB行锁是通过给索引上的索引项加锁来实现的InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

举个例子:

我们创建如下表,没有任何索引

mysql> desc t3;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id   | int(11) | YES  |     | NULL   |       |
| sale | int(11) | YES  |     | NULL   |       |
+-------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)

A会话

B会话

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t3 where id=3 for update;
Empty set (0.00 sec)

 

 

mysql> select * from t3 where id=1 for update;阻塞等待

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

 

 

mysql> select * from t3 where id=1 for update;
+------+------+
| id   | sale |
+------+------+
|    1 | 1000 |
+------+------+
1 row in set (4.27 sec)

注意:

由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。

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

间隙锁(Next-Key锁)

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

举个例子:

mysql> desc t;
+-------+---------+------+-----+---------+-------+
| Field |Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| t     | int(11) | YES  | MUL | NULL    |      |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00sec)

A会话

B会话

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where t<10 for update;
+------+
| t    |
+------+
|    2 |
+------+
1 row in set (0.00 sec)

 

 

mysql> insert into t values (1);
阻塞等待

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

 

 

mysql> insert into t values (1);
Query OK, 1 row affected (5.88 sec)

死锁

死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。

一种经典情况:A等待B,B在等待A,这种死锁问题称为AB-BA死锁。

A会话

B会话

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where t=1 for update;
Empty set (0.00 sec)


mysql> select * from t3 where id=1 for update;
+------+------+
| id   | sale |
+------+------+
|    1 | 1000 |
+------+------+
1 row in set (0.01 sec)

 

mysql> insert into t  values (1);

mysql> select * from t3 where id=1 for update;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

 

 

mysql> insert into t  values (1);
Query OK, 1 row affected (8.31 sec)

另一中常见的锁死情况,即当前事务持有了带插入记录的下一个记录的X锁,但是在等待队列中存在一个S锁的请求,则可能会发生锁死。

例如:

A对话

B对话

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t where t=2 for update;
+------+
| t    |
+------+
|    2 |
+------+
1 row in set (0.01 sec)

 

 

mysql> select * from t where t<3 lock in share mode;
等待

Mysql> insert into t values(0);
Query OK, 1 row affected (0.01 sec)


mysql> select * from t where t<3 lock in share mode;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting trans
Action

因为B对话对t=0行加了共享锁,并且A对话插入t=0数据与B对话产生阻塞,再加上B对话需要对t=2获得共享锁因为A对话拥有t=2的排他锁所以也需要等待。所以产生了死锁。

锁问题

通过锁定机制可以实现事物的隔离性要求,使得事务可以并发地工作。锁提高了并发,但是却带来潜在的问题。问题如下:

l  脏读

所谓的脏读是指事务对缓冲池中行记录的修改,并且还没有提交。

举个例子:

会话A

会话B

set session transaction isolation level read uncommitted;

set session transaction isolation level read uncommitted;

mysql> select * from t;
Empty set (0.01 sec)

 


mysql> insert into t values (2);
Query OK, 1 row affected (0.01 sec)

mysql> select * from t;
+------+
| t    |
+------+
|    2 |
+------+
1 row in set (0.00 sec)

 

我们发现在不同的时间段,在事务两次次select的结果都不相同。其中两次记录是在B未提交的数据,即产生了脏读,违反了事物的隔离性。

l  不可重复读

不可重复读是指在一个事务内多次读取同一个数据集合。在一个事务还没有结束时,另外一个事务也访问该同一数据集合,并做了DML操作。因此,在第一个事务中的两次数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。

不可重复读与脏读的区别是:
脏读是读到未提交的数据,而不可重复读到的却是已提交的数据,但是其违反了数据库事务的一致性的要求。

举例子,

A会话

B会话

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t values (2);
Query OK, 1 row affected (0.00 sec)

 

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

 

 

	
mysql> select * from t;
+------+
| t    |
+------+
|    2 |
|    2 |
+------+
2 rows in set (0.00 sec)

l  丢失更新

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新。

l  幻读

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

mysql> create table t2 (id intprimary key);

A会话

B会话

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

 

mysql> select * from t2;
Empty set (0.00 sec)

mysql> insert into t2 values(1);
Query OK, 1 row affected (0.00 sec)

 

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

 


mysql> select * from t2;
Empty set (0.00 sec)

 

mysql> insert into t2 values(1);
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'

四种隔离级别对这些问题的解决情况:

隔离级别

脏读

不可重复读

幻读

Read uncommitted

未解决

未解决

未解决

Read committed

解决

未解决

未解决

Repeatable read

解决

解决

未解决

Serializable

解决

解决

解决



你可能感兴趣的:(Mysql学习笔记)