• Welcome to Journal web site.

我是 PHP 程序员

- 开发无止境 -

Next
Prev

数据库事务隔离级别和锁 - joker1034

Data: 2020-01-08 21:51:11Form: JournalClick: 14

joker1034
数据库事务隔离级别和锁 - joker1034 - 博客园

 

共享锁(Shared lock)

例1:


T1: select * from table (请想象它需要执行1个小时之久,后面的sql语句请都这么想象)

T2: update table set column1='hello'

 

过程:T1运行 (加共享锁)

T2运行等待T1运行完之后再运行T2

 

分析:之所以要等,是因为T2在执行update前,试图对table表加一个排他锁,而数据库规定同一资源上不能同时共存共享锁和排他锁。所以T2必须等T1执行完,释放了共享锁,才能加上排他锁,然后才能开始执行update语句。

 

例2:


T1: select * from table

T2: select * from table

 

这里T2不用等待T1执行完,而是可以马上执行。

 

分析:

 

T1运行,则table被加锁,比如叫lockA

T2运行,再对table加一个共享锁,比如叫lockB。

 

两个锁是可以同时存在于同一资源上的(比如同一个表上)。这被称为共享锁与共享锁兼容。这意味着共享锁不阻止其它session同时读资源,但阻止其它session update

 

例3:


T1: select * from table

T2: select * from table

T3: update table set column1='hello'

 

这次,T2不用等T1运行完就能运行,T3却要等T1和T2都运行完才能运行。因为T3必须等T1和T2的共享锁全部释放才能进行加排他锁然后执行update操作。

 

死锁的发生

例4:


 

T1:begin tran

select * from table (holdlock) (holdlock意思是加共享锁,直到事务结束才释放)

update table set column1='hello'

 

T2:begin tran

select * from table(holdlock)

update table set column1='world'

 

假设T1和T2同时达到select,T1对table加共享锁,T2也对加共享锁,当T1的select执行完,准备执行update时,根据锁机制,T1的共享锁需要升级到排他锁才能执行接下来的update.在升级排他锁前,必须等table上的其它共享锁释放,但因为holdlock这样的共享锁只有等事务结束后才释放,所以因为T2的共享锁不释放而导致T1等(等T2释放共享锁,自己好升级成排他锁),同理,也因为T1的共享锁不释放而导致T2等。死锁产生了。

 

排他锁

例5:


T1:begin tran

update table set column1='hello' where id=10

 

T2:begin tran

update table set column1='world' where id=20

 

这种语句虽然最为常见,很多人觉得它有机会产生死锁,但实际上要看情况,如果id是主键上面有索引,那么T1会一下子找到该条记录(id=10的记录),然后对该条记录加排他锁,T2,同样,一下子通过索引定位到记录,然后对id=20的记录加排他锁,这样T1和T2各更新各的,互不影响。T2也不需要等。

 

但如果id是普通的一列,没有索引。那么当T1对id=10这一行加排他锁后,T2为了找到id=20,需要对全表扫描,那么就会预先对表加上共享锁或更新锁或排他锁(依赖于数据库执行策略和方式,比如第一次执行和第二次执行数据库执行策略就会不同)。但因为T1已经为一条记录加了排他锁,导致T2的全表扫描进行不下去,就导致T2等待。

 

死锁怎么解决呢?一种办法是,如下:

例6:


T1:begin tran

select * from table(xlock) (xlock意思是直接对表加排他锁)

update table set column1='hello'

 

T2:begin tran

select * from table(xlock)

update table set column1='world'

 

这样,当T1的select 执行时,直接对表加上了排他锁,T2在执行select时,就需要等T1事务完全执行完才能执行。排除了死锁发生。但当第三个user过来想执行一个查询语句时,也因为排他锁的存在而不得不等待,第四个、第五个user也会因此而等待。在大并发情况下,让大家等待显得性能就太友好了,所以,这里引入了更新锁。

 

更新锁(Update lock)

为解决死锁,引入更新锁

 

例7:


T1:begin tran

select * from table(updlock) (加更新锁)

update table set column1='hello'

 

T2:begin tran

select * from table(updlock)

update table set column1='world'

 

更新锁的意思是:“我现在只想读,你们别人也可以读,但我将来可能会做更新操作,我已经获取了从共享锁(用来读)到排他锁(用来更新)的资格”。一个事务只能有一个更新锁获此资格。

 

T1执行select,加更新锁。

T2运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。(T2加的是更新锁,更新锁与更新锁不兼容, 如果加的是共享锁, 共享锁和更新锁可以兼容,即T1,T2不可同时进行,但是T3,T4,T5只要不是事务,还是可以正常查询)

 

当后来有user3、user4...需要查询table表中的数据时,并不会因为T1的select在执行就被阻塞,照样能查询,相比起例6,这提高了效率

 

例8:


T1:begin

select * from table(updlock) (加更新锁)

update table set column1='hello' (

Name:
<提交>