MYSQL 锁

十年热恋 提交于 2019-11-28 13:45:53

锁的类型

共享锁(S)

共享锁可以被多个事务持有,事务T1获取了行r的共享锁,那么事务T2也可以获得行r的共享锁,但是获取不到事务T1的排它锁

显示的加共享锁:select * from xx lock in share mode;

排它锁(X)

排他锁只能被一个事务持有,事务T1持有了行r的排它锁,那么事务T2获取不到行r的排它锁,也获取不到行r的共享锁

显式的加排它锁:select * from xx for update;

 

S

X

S

兼容

不兼容

X

不兼容

不兼容

创建一张测试表a,id是主键,a2为唯一索引,a3为普通所以

CREATE TABLE `a` (
  `id` int(11) NOT NULL AUTO_INCREMENT ,
  a1 int(11) NOT NULL ,
  a2 int(11) not NULL,
  a3 int(11) not null,
  PRIMARY KEY (`id`),
  unique key uniq_a2(a2),
  index idx_a3(a3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
insert into a(a1,a2,a3) values(1,2,3),(2,4,6),(3,6,12);

 

事务1获取共享锁,事务2获取排它锁,事务2将获取失败

事务1

事务2

start TRANSACTION;

select * from a where id=2 lock in share mode;

 

获取S锁成功

 
 

start TRANSACTION;

select * from a where id=2 for update;

 

等待...

 

获取X锁超时(Lock wait timeout exceeded; try restarting transaction)

commit;

 
 

start TRANSACTION;

select * from a where id=2 for update;

   

在事务2等待锁过程中执行 select * from information_schema.innodb_trx; 查看锁当前状态,可以看到事务1处于RUNNING状态,事务2处于LOCK WAIT状态

其中trx_weight表示事务的权重,反应了一个事务修改和锁住的行数。在INNODB中,当发生死锁需要回滚时,InnoDB会选择该值最小的进行回滚,因为这个值越小需要进行回滚操作的数据越少

在事务2等待锁过程中执行 select * from information_schema.innodb_locks 查看锁信息,lock_data表示事务锁定的主键的值,由于查询条件是id=2,因此lock_data=2,由于两个事务对同一行记录分别申请S所和X锁,因此会发生等待

lock_rec 事务锁定行的数量(明明是按主键锁主了一条记录,为什么这里的lock_rec是3)

lock_page 事务锁定页的数量

 

 

使用 select * from information_schema.innodb_lock_waits; 查看信息,可以看到那些事务获取到了锁,那些事务在阻塞中

 

一致性非阻塞读(consistent nonblocking read)

一致性非阻塞读表示存储引擎通过MVCC方式读取当前时间数据库中的数据,如果正在读取的行正在执行delete或者update操作,读操作被会被阻塞,而是会读取该行记录的快照数据。

在事务隔离级别为READ COMMITTED 和 REPEATABLE READ 下,InnoDB采用非阻塞读方式。但是在READ COMMITTED隔离级别下,读取到的快照数据是最新的一个快照数据(不可重复读),而在REPEATABLE READ模式下,读取到的快照数据总是在事务开始时读取到的快照数据(可重复读)

REPEATABLE READ隔离级别下,在同一个事务内,读取到的数据总是一致的(因为msyql默认的隔离级别是RR,因此我们使用RR测试)

会话A

会话B

start TRANSACTION;

 

select * from a where id=1;(a1=1)

 
 

start TRANSACTION;

 

select * from a where id=1; (a1=10)

select * from a where id=1;(a1=1)

 
 

commit;

select * from a where id=1;(a1=1)

 

commit;

 

select * from a where id=1;(a1=10)

 

在READ COMMITTED 隔离级别下,在事务内总是读取到最新的快照,也就是可以读取到别的事务提交的数据。(需要手动设置隔离级别为RC,同时将列a1的值重置为1)

会话A

会话B

start TRANSACTION;

 

select * from a where id=1;(a1=1)

 
 

start TRANSACTION;

 

select * from a where id=1; (a1=10)

select * from a where id=1;(a1=1)

 
 

commit;

select * from a where id=1;(a1=10)

 

commit;

 

select * from a where id=1;(a1=10)

 

一致性阻塞读

行锁的3种算法

Record Lock: 单行记录上的锁,锁住的是索引记录,如果表没有任何创建索引,那么InnoDB默认使用隐式的主键进行锁定

Gap Lock:间隙锁,锁定一个范围,不包含记录本身

Next-Key Lock:Gap+Record Lock,锁定一个范围,包括记录本身

如果采用Next-Key Lock算法,那么针对表a中的列a3(普通索引,值分为为 3,6,12),那么可能被Next-Key Locking的区间为:

(-∞, 3], (3,6], (6,12], (12,+∞)

采用Next-Key Lock的锁定技术称为Next-Key Locking,目的是为了解决幻读问题(Phantom Problem),除了next-key locking ,还有previous-key locking 技术,如果采用previous-key locking,那么上述a3可锁定的区间为:

(-∞, 3), [3,6), [6,12), [12,+∞)

当查询的索引是唯一索引时,InnoDB会对Next-Key Locking进行优化,降级为Record Lock,值锁住索引本身,而不是范围

 

以下针对列a2进行测试,a2为唯一索引,a2的值为 2,4,6

在Next-Key Lock算法下,可锁住的区间为(-∞, 2], (2,4], (4,6], (6,+∞),5和3处于索引范围内,会话B获取5和3的锁应该要等待,但是由于a2是唯一索引,因此降级为Record Lock,仅锁住a2=4本身,所以会话B不会被阻塞

会话A

会话B

start TRANSACTION;

 

select * from a where a2=4 for update;

 
 

start TRANSACTION;

 

select * from a where a2=5 for update;(成功,不需要等待)

 

select * from a where a2=3 for update;(成功,不需要等待)

 

commit;

commit;

 
   

 

看下普通索引在Next-key Lock算法下的情况

使用表a的列a1测试,a1为普通索引

采用Next-Key Lock 算法,可锁定的区间为(-∞, 1), (1,2), (2,3),(3,9), (9,+∞),

会话A

会话B

start TRANSACTION;

 

select * from a where a1=3 for update;

 
 

start TRANSACTION;

 

select * from a where a1=5 for update;(获取锁不成功,因为5处于(3,9]之间)

 

select * from a where a1=2 for update;(获取锁不成功,因为2处于锁定范围内)

 

select * from a where a1=3 lock in share mode; (获取锁不成功,因为3本身已被锁定)

 

select * from a where id=1 for update;(获取锁不成功) 为什么1会被锁定

 

select * from a where id=10 for update;(获取锁成功,因为会话A锁定的范围是[1,9),10不在锁定范围内,因此获取锁成功)为什么锁定范围不是(2,9)

 

select * from a where id=0 for update;(获取锁成功)

死锁

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

解决死锁最简单的方式是不要有等待,将任何的等待转化为回滚。两个事务相互等待,当其中一个等待时间超过设置的某一个阈值,根据权重,将其中一个事务回滚,另外一个事务就可以正常运行。

权重在前面提到过(txr_weight),权重的大小取决于事务修改和锁定的行数,更新的行数越小,占用的undo log 也就越少,回滚需要的时间也就越少

测试下死锁

会话A

会话B

会话A

会话B

start TRANSACTION;

 

delete from a where id=3; (成功)

 
 

start TRANSACTION;

 

delete from a where id=2;(成功)

delete from a where id=2;(等待)

 
 

delete from a where id=3; (等待)

 

超时回滚其中一个事务

show engine innodb status ; 查看死锁日志

 
=====================================
2019-07-26 02:02:56 0x70000b18b000 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 15 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 84 srv_active, 0 srv_shutdown, 167877 srv_idle
srv_master_thread log flush and writes: 167921
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 155
OS WAIT ARRAY INFO: signal count 154
RW-shared spins 0, rounds 270, OS waits 133
RW-excl spins 0, rounds 666, OS waits 6
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 270.00 RW-shared, 666.00 RW-excl, 0.00 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2019-07-26 02:02:42 0x70000b037000
*** (1) TRANSACTION:
TRANSACTION 53206, ACTIVE 10 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 25, OS thread handle 123145487921152, query id 2377 localhost 127.0.0.1 root updating
delete from a where id=3
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 246 page no 3 n bits 80 index PRIMARY of table `hotel_mint`.`a` trx id 53206 lock_mode X locks rec but not gap waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 6; compact format; info bits 32
 0: len 4; hex 80000003; asc     ;;
 1: len 6; hex 00000000cfd1; asc       ;;
 2: len 7; hex 220000012822e4; asc "   (" ;;
 3: len 4; hex 80000003; asc     ;;
 4: len 4; hex 80000006; asc     ;;
 5: len 4; hex 8000000c; asc     ;;

*** (2) TRANSACTION:
TRANSACTION 53201, ACTIVE 13 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 23, OS thread handle 123145487085568, query id 2378 localhost 127.0.0.1 root updating
delete from a where id=2
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 246 page no 3 n bits 80 index PRIMARY of table `hotel_mint`.`a` trx id 53201 lock_mode X locks rec but not gap
Record lock, heap no 4 PHYSICAL RECORD: n_fields 6; compact format; info bits 32
 0: len 4; hex 80000003; asc     ;;
 1: len 6; hex 00000000cfd1; asc       ;;
 2: len 7; hex 220000012822e4; asc "   (" ;;
 3: len 4; hex 80000003; asc     ;;
 4: len 4; hex 80000006; asc     ;;
 5: len 4; hex 8000000c; asc     ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 246 page no 3 n bits 80 index PRIMARY of table `hotel_mint`.`a` trx id 53201 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 32
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000000cfd6; asc       ;;
 2: len 7; hex 250000030f0d82; asc %      ;;
 3: len 4; hex 80000002; asc     ;;
 4: len 4; hex 80000004; asc     ;;
 5: len 4; hex 80000006; asc     ;;

*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS
------------
Trx id counter 53212
Purge done for trx's n:o < 53212 undo n:o < 0 state: running but idle
History list length 52
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 281479538886448, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 281479538887352, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 53206, ACTIVE 24 sec
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 2
MySQL thread id 25, OS thread handle 123145487921152, query id 2380 localhost 127.0.0.1 root
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (read thread)
I/O thread 4 state: waiting for i/o request (read thread)
I/O thread 5 state: waiting for i/o request (read thread)
I/O thread 6 state: waiting for i/o request (write thread)
I/O thread 7 state: waiting for i/o request (write thread)
I/O thread 8 state: waiting for i/o request (write thread)
I/O thread 9 state: waiting for i/o request (write thread)
Pending normal aio reads: [0, 0, 0, 0] , aio writes: [0, 0, 0, 0] ,
 ibuf aio reads:, log i/o's:, sync i/o's:
Pending flushes (fsync) log: 0; buffer pool: 0
2182 OS file reads, 1151 OS file writes, 505 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 1.53 writes/s, 0.80 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 34673, node heap has 0 buffer(s)
Hash table size 34673, node heap has 0 buffer(s)
Hash table size 34673, node heap has 0 buffer(s)
Hash table size 34673, node heap has 0 buffer(s)
Hash table size 34673, node heap has 1 buffer(s)
Hash table size 34673, node heap has 0 buffer(s)
Hash table size 34673, node heap has 0 buffer(s)
Hash table size 34673, node heap has 0 buffer(s)
1.53 hash searches/s, 1.13 non-hash searches/s
---
LOG
---
Log sequence number 3022251932
Log flushed up to   3022251932
Pages flushed up to 3022251932
Last checkpoint at  3022251923
0 pending log flushes, 0 pending chkp writes
306 log i/o's done, 0.47 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 439113
Buffer pool size   8191
Free buffers       5999
Database pages     2191
Old database pages 818
Modified db pages  0
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 2109, created 82, written 769
0.00 reads/s, 0.07 creates/s, 1.00 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 2191, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Process ID=103, Main thread ID=123145481420800, state: sleeping
Number of rows inserted 5879, updated 11, deleted 11, read 798826
0.00 inserts/s, 0.00 updates/s, 0.07 deletes/s, 0.07 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================
View Code

 

避免死锁的方法

1、系统中事务的数量尽量少

2、避免大事务,事务尽量小

3、避免大sql,能拆则拆,避免长时间占用资源,导致其他事务等待或者与其他事务冲突发生死锁

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!