您的位置:首页 > 数据库

数据库锁机制

2017-09-15 00:37 197 查看

一、锁的种类

1、共享锁(Shared lock)

共享锁与共享锁兼容,与更新锁兼容;

共享锁与排他锁不兼容。

例1:

T1:select * from table;

T2:update table set name = 'Dkangel';

select 默认情况下是加共享锁,update是加排他锁

T1运行时对table加共享锁,此时分为两种情况:

第一种:T1运行完成,table上的共享锁被释放,T2运行并在table上加排他锁然后执行sql语句。

第二种:T2等待T1运行完成,由于数据库规定同一资源不能同时存在共享锁和排他锁,所以T2必须等T1执行完成释放共享锁,     才能加排他锁然后执行sql语句。

例2:

T1:select * from table;

T2:select * from table;

不管T1是否执行结束,T2都可以执行sql语句。

T1执行加共享锁T2执行也加共享锁,两个共享锁可以同时在一个资源上,被称为共享锁和共享锁兼容,共享锁不会阻止其他session

读取资源,而会阻止其他session修改资源。

例3(死锁):

T1:

begin tran

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

update table set name = 'Dkangel';

T2:

begin tran

select *form table
(holdlock) ;

update table set name = 'Dkangel';

存在这种情况,T1和T2同时到达select,T1、T2都对table加了共享锁,T1的select执行完毕准备执行update语句,根据锁机制T1的

共享锁需要升级为排他锁才能执行update,在升级之前必须等待table上的其他共享锁被释放,但是因为holdlock这样的共享锁得等事务结束才能释放,所以T1得等T2的hold共享锁释放,才能升级为排他锁。同理T2也在等T1的hold共享锁释放,就造成了死锁。

例4(解决死锁)

T1:

begin tran

select *form table (xlock) (xlock意思是加排他锁)
<
4000
/p>
update table set name = 'Dkangel';

T2:

begin tran

select *form table (xlock)
;

update table set name = 'Dkangel';

T3:

select *from table;

T1执行select时在table上加排他锁,T2要执行select时必须等T1事物执行结束将排他锁被释放,这样就排除了死锁的产生。

但是,当T3想执行查询语句的时候,就不得不等待排他锁的释放,在大并发的情况下有很多用户会执行类似于T3的查询语句,这样就造成了用户的长时间等待,所以就需要有更新锁的存在。

例5

T1:

begin tran

update table set name = 'Dkangel' where id = 1;

T2:

begin tran

update table set name = 'Dkangel' where id = 2;

存在两种情况:

第一种:如果id是主键,则T1找到id为1的这条记录,然后在这条记录上加上排他锁,锁行。当T2执行是找到id为2的记录,并加上行

排他锁,各更新各的互不影响。

第二种:如果id不是主键,T1找到id为1的这条记录并加上排他锁,T2为了找到id为2的记录就要对table进行全表扫描,那么就会预先
对表加上锁,但是由于T1以及加了排他锁,所以导致T2无法进行扫描,导致T2等待。

2、更新锁(Update lock)

更新锁是:“我获取了能从共享锁升级到排他锁的资格,假如我在进行读操作,你们也可以进行读操作,但是我待会可能会做更新操作,所以你们要做更新操作得等到我释放更新锁之后”。事物只能有一个更新锁获得该资格。

更新锁与共享锁兼容;

更新锁与更新锁不兼容,与排他锁不兼容;

例6、

T1:

begin tran

select *from table(uplock);(加更新锁)

update table set name = 'Dkangel'

T2:

begin tran

select *from table(uplock);

update table  set name = 'Dkangel';

T3:

select *from table;

T1执行select语句时在table上加更新锁,当T2要执行的时候发现table上已经有了更新锁,只好等待。

T3是读数据操作执行的时候不需要等待,类似于T3的用户无须像T2一样等待,相比例4不仅排出了死锁还提高了效率。

加深

将T2的更新锁变为共享锁:

第一种情况:

T1先到达T2到达,T1先对表加更新锁T2加共享锁,假设T2先执行完select准备执行update,发现表已经存在更新锁,进入等待。T1执行完select后将更新锁升级为排他锁,执行update,T1执行结束事物完成后,T2将共享锁升级为排他锁执行update。

第二种情况:

T2比T1先到达,T2加共享锁T1加更新锁,假设T2先执行完select后准备将共享锁升级为排他锁,但是发现存在T1的更新锁所以进入等待状态,后面的步骤跟情况1一样了。

说明更新锁和排他锁不兼容,不能同时存在于一个资源上。

例7、

T1:

select *from table(uplock);(加更新锁)

T2:

select *from table(uplock);

T3:

select *from table;

T1执行,因为同一个资源上不能同时存在多个更新锁,所以T2必须等待table上的更新锁被释放才能执行,T3无须等待直接执行。

说明更新锁和共享锁可以同时存在于同一个资源,共享锁和更新锁是兼容的。

3、排他锁(独占锁,Exclusive Locks)

排他锁与排他锁不兼容,与共享锁不兼容,与更新锁不兼容。

例8、

T1:update table set name = 'Dkangel' where id<10;

T2:update table set name = 'Dkangel' where id>10;

假如T1先到达,则T1会对id<10的数据加排它锁,而不会影响T2的update。

4、意向锁(Intent Locks)

意向锁:比如窝子外有一个标识,表示里有人被锁住了,想知道有没有被锁住了,不用进去屋子里看标识就可以了。

当一个表的某条记录被加了排他锁后,该表就不能加其他表锁了。数据库系统怎么知道该表还能不能被加锁?一种是逐条的判断该表中的记录是否被加了排他锁;另一种是直接在表这一次加意向锁,不需要逐条判断。这样效率更高。

例9、

T1:

begin tran

select *from table (xlock) where id = 10; (加排他锁)

T2:

begin tran

select *form table (tablock); (加某种表级锁)

假设T1先执行,当T2执行的时候先判断该表是否可以加表锁,数据库系统需要逐条判断table中是否有记录被加了排他锁,如果有记录被加了排他锁,则该表不能加表锁。这样的效率太低了。

实际上,数据库系统在T1执行select在id=10这条记录上加排他锁的同时也对table表加了意向排他锁,当T2执行select要加表级锁时,判断table上是否有意向排他锁,如果有直接等待,不需要进行逐条判断记录上是否有排他锁。

例10、

T1:update table set name = 'Dkangel' where id = 10;

T2:update table set name = 'Dkangel' where id = 1;

同上,T1执行,系统对id=10的记录加排他锁,对页加意向排他锁,对表加意向排他锁。

5、计划锁

例11、

alter table table_name ......(add time datetime)

DDL(数据定义语言)都会加计划锁

该锁不允许其他session连接该表,所以不会执行sql语句。

例12、

jdbc向数据库发送一条新的sql语句,数据库先对其进行编译,在编译期间也会加锁。

select *from table;

编译这条语句过程中其他session可以对该表进行任何操作(update、delete、加排它锁等),但是并不能做DDL操作。

6、大容量更新锁(Bulk Update Locks) 

当数据大量复制到表中时,数据库引擎将使用了大容量更新 (BU) 锁。
大容量更新锁(BU 锁)允许多个线程将数据并发地大容量加载到同一表, 同时防止其他不进行大容量加载数据的进程访问该表.

二、何时加锁

可以通过手动hint强行指定,但大多时候是数据库系统自动决定的,这就是为何我们不懂锁还能编写sql语句。

脏读:

脏读是指一个事物T1正在访问数据,并对数据进行了修改,但是并没有提交修改。这时另一个事物T2也访问该数据,并使用了该数据,由于T1没有提交修改,所有T2使用的数据是脏数据,使用脏数据的操作可能是不正确的。

例13、

T1:

begin tran

update table set name='Dkangel' where id = 10;

T2:

set transaction isolation read uncommitted    ---事物隔离级别为允许脏读

go

select *from table where id = 10;

数据库如何自动加锁:

T1执行,数据库自动加排他锁,

T2执行,事务隔离级别设置为允许脏读,便不加共享锁,不会与排他锁冲突,所以可以读出数据。

如果事物隔离级别不设为脏读:

T1执行,数据库自动加排他锁,

T2执行,准备加共享锁,发现table上有排他锁,所以进入等待直到T1执行结束释放排他锁才能执行。

三、锁的粒度

锁的粒度是指锁的作用范围,行级锁、表级锁、页级锁,锁的范围可以通过手动hint来管理,也可以有数据库自动管理。

例14、

T1:select *from table (paglock);

T2:update table set name = 'Dkangel' where id>10;

T1执行时会在第一页上加锁,读完第一页释放锁再在第二页加上,锁依此类推。

假如前10行数据是第一页,那么T1执行第一页查询的时候并不会阻止T2的执行。假如第一页只有9条数据,那么T2得等T1查询完第二页并释放锁之后才能执行更新。

例15、

T1:select *from table (rowlock)

T2:update table set name = 'Dkangel' where id = 10;

T1执行时,对每一行加共享锁,读取,释放,然后在对下一行加锁,依此类推;T2执行时,先判断id=10这条数据上是否有行锁,如果没有则加上排他锁更新,如果有则等待锁被释放然后加排他锁。

例16、

T1:select *from table (tablock);

T2:update table set name = 'Dkangel' where id = 10;

T1执行,对整个表加共享锁,只有当T1执行完释放共享锁后T2才能加锁执行。

以上是通过手动指定锁的粒度,也可以通过设定事务隔离级别,让数据库自动设置锁的粒度。不同的事务隔离级别,数据库会有不同的加锁策略(类型、粒度)。

四、锁与事物隔离级别的优先级

手动指定的锁优先级高

五、数据库其他重要的hint以及他们的区别

1、holdlock对表加共享锁,事物不完成,共享锁不是释放。

2、tablock  对表加共享锁,只要statement不完成,共享锁不是释放。

例17、

T1:

begin tran

select *from table (tablock);

T2:

begin tran

update table set name = 'Dkangel' where id = 10;

T1执行结束,释放共享锁,T2就可以加排他锁执行update。

例18、

T1:

begin tran

select *from table (holdlock);

T2:

begin tran

update table set name = 'Dkangel' where id = 10;

 T1执行结束,共享锁仍然不会被释放,仍然会被hold(持有),T2必须等待。当T1最后执行commit或者rollback说明事物结束,T2才可以执行更新。

3、tablockx对表加排他锁

例19、

T1:select *from table (tablockx);

其他session无法对表执行查询或者更新,当T1执行完,会自动释放排他锁。

例20、

T1:

begin tran 

select *from table (tablockx);

当T1执行完并不会释放排他锁,必须整个事物执行完成(commit或rollback)后才会释放排他锁。

4、xlock 加排他锁

例20、

select *from table(xlock paglock) 对page加排他锁

而tablockx不能这么用,xlock还可这么用:select * from table(xlock tablock) 效果等同于select * from table(tablockx)。

六、锁的超时等待

T1:

begin tran

update table set name = 'Dkangel' where id = 10;

T2:

set lock_timelock 4000

select *from table;

T2执行,会等待4秒让T1释放排他锁,超过4秒则T2将抛出异常:Lock request time out period exceeded.

七、如何提高并发效率

1、悲观锁:利用数据库本身的机制实现。

可以根据具体的业务情况综合使用事务隔离级别和合理的手动指定锁的方式。比如降低锁的粒度等减少并发等待。

2、乐观锁:利用程序处理并发。方式大概有以下3种

对记录加版本号;

对记录加时间戳;

对将要跟新的数据提前读取、事后比较。

不论是数据库系统本身的锁机制,还是乐观锁这种业务数据级别的锁机制,本质上都是对状态位的读、写、判断。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: