05 锁是并发的天敌,为什么少不了它?

为什么需要锁

《事务是怎么练成的》章节,我们介绍了四种类型的隔离级别,串行化隔离级别是最理想的,但是却会的数据库的处理性能造成灾难级的影响。无法想象,对于一个支撑上千甚至上万客户端并发操作的系统中使用串行化隔离级别,是一个什么样的场景。

通过隔离级别,规避了多并发场景中事务之间的可能造成的干扰,让数据库能够允许更多的操作同时执行,比如绝大多数情况下,读都不会被阻塞。但是仍然有很多的场景需要串行来操作,现实世界中有很多这样的例子,比如去超市购物,结账的时候需要排队;乘高铁的时候,需要依次通过闸机等等。那么这些场景在数据库中怎么实现呢,答案是锁。

和隔离级别相反,数据库锁的设计是为了防止无限度的并发操作。作为多用户共享的资源,当需要并发访问的时候,数据库需要合理的控制资源的访问规则,而锁是用来实现这些访问规则的重要数据结构。

排它锁和共享锁

锁是并发的天敌,为了提高数据库的并发处理能力,锁的设计至关重要。为了应对不同的场景,Oracle将锁分成了排它锁(eXclusive)和共享锁(Shared)两种模式。

排它锁

排它锁是独占类型的,如果某事务对相关数据或数据对象加上排它锁后,则其他事务不能再对这些数据加任何类型的锁,因此又称为独占锁、互斥锁。当事务执行DML操作时,数据库会自动为这些数据加上排它锁,一旦加上排它锁,不允许其他事务再对这些数据对象上加其他锁。

排它锁主要用于防止多个事务同时操作某些数据,破坏数据的完整性和一致性。

共享锁

共享锁,相比于排它锁,共享锁允许多个事务同时对数据库特定资源加上相同类型的锁。当事务执行DML操作时,数据库会在相关对象上加上共享锁;如果有其他事务需要更新其他的数据行,数据库会在这个对象上再加一个共享锁。

共享锁相互之间不阻塞,主要是为了防止事务执行期间,相关对象被加上更高级别的排它锁。

行级锁和表级锁

根据锁定对象的不同,Oracle数据库中主要的锁有两种:行级锁(Row Lock)和表级锁(Table Lock),可以看做是排它锁和共享锁的具体实现。另外内存中的Latch、Mutex等也是一种锁,但本节内容中我们不展开,留待后续的文章中详细讲。

行级锁

行级锁是排它锁,同一个数据资源不能同时加上两个行级锁。

为了帮助大家理解这行级锁的概念和作用,这里仍然以银行转账的操作为例,做一个说明。

## session 1
## Xiaoming向Dahong账号转账1000元
## Xiaoming账号扣减1000,Dahong账号增加1000
update account set balance = balance - 1000 where name = 'Xiaoming';
update account set balance = balance + 1000 where name = 'Dahong';

## session 2
## Dahong向账号Zhangsan转账1000元
## Dahong账号扣减1000,Zhangsan账号增加1000
update account set balance = balance - 1000 where name = 'Dahong';
update account set balance = balance + 1000 where name = 'Zhangsan';

假设Xiaoming对Dahong账号发起转账,几乎同时Dahong也在向Zhangsan账号发起转账,数据库中分别有两个会话来处理这两笔交易。如果session 1执行完第二条语句(更新Dahong的账号)之后事务没有及时结束,此时session 2在执行第一条语句时,将会出现长时间的等待。如果此时去查等待事件,将会看到session 2在等待"enq: TX contention"事件(这是Oracle数据库中非常常见的等待),表示session 2需要获取的资源被其他会话所持有,必须等待其他会话释放后才能继续之后的操作。

之所以会发生阻塞,根本原因就是session 1在更新Dahong的账户余额时,在这条记录上加了一个行级锁,这个锁会持有到session 1的事务结束,在此之前任何其他会话想要更新Dahong的账户余额,必须等到session 1事务结束释放锁资源。

这里行级锁的功能就是为了防止多个事务同时操作特定数据资源的时候出现混乱,强制保证串行化操作而专门设计的。有同学可能会认为这会降低数据库的并发能力,影响应用性能,但这是为了保证数据库一致性和完整性必须要承担的代价。大家想想看,如果两条更新Dahong账户余额的语句执行顺序反过来(先执行session 2的扣减1000元)会怎样?如果Dahong账户的初始余额是零呢,银行会运行余额为零仍然对外转账吗?

所谓行级锁,只针对被操作的行数据进行加锁,所以这个时候如果更新账户余额表中其他账号的信息,是不受影响的。这也是数据库为了减少锁对业务的影响,尽量将锁粒度降到最低。

表级锁

事实上,在事务对数据进行操作时,除了前面说的行级锁之外,Oracle还会在表上添加一个表级锁。表级锁是共享锁,同一个数据资源上可以添加多个同级别的表级锁,其存在的意义主要是为了防止在事务未结束时,对表等对象上进行其他更高级别的操作,如truncate表、删除表等等。

因此在我们前面银行转账的例子中,session 1和session 2分别会在表account上添加一个表级共享锁,这个锁的存在并不会对事务更新数据造成影响,但如果此时有第三个会话尝试删除表,则会报出ORA-00054的错误,提示用户当前表资源已经被其他会话锁定,无法获取到资源。

ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired

为了实现最小化的加锁粒度,Oracle数据库中的表级锁共分为0~6,一共7个级别。不同的操作对应不同级别的锁,为了大家查阅方便,7类表级锁的使用场景简单列举如下。

锁级别锁名称SQL操作适用场景
0 (None)Select不加锁
1 (Null)偶尔会有Select出现在v$locked_object不加锁
2 (RS)行级共享锁Select for update、Lock for update、Lock row share行级共享锁,其他对象只能查询这些数据行;是锁的类型中限制最少的锁,也是在表的并发程度中使用最多的
3 (RX)行级排它锁Insert、Update、 Delete、Lock row share行级排它锁,在提交前不允许做DML操作
4 (S)共享锁Create Index, Lock Share阻止其他DDL操作
5 (SRX)共享行级排它锁Lock Share Row Exclusive阻止其他事务操作
6 (X)排它锁Alter table、Drop able、Drop index、Truncate table 、Lock exclusive级别最高的表级锁

Oracle行锁和事务槽(ITL)

Oracle的锁机制是一种轻量级的锁定机制,不通过构建锁列表来进行数据的锁定管理,而是直接将锁作为数据块的属性,存储在数据块的头部。Oracle每个数据块头部都预留了一部分被称为事务槽(ITL, Interested Transaction List)的空间,其中记录了事务ID、对应的Undo回滚段、事务状态以及SCN等信息,通过这些信息Oracle可以判断出事务的状态以及对应的数据行加锁的情况。

Block header dump:  0x01400003
 Object id on Block? Y
 seg/obj: 0xb70  csc: 0x00.e0c76  itc: 1  flg: O  typ: 1 - DATA
     fsl: 0  fnx: 0x0 ver: 0x01

 Itl           Xid                  Uba               Flag  Lck  Scn/Fsc
0x01 xid:0x0004.053.00000004  uba:0x00c00616.0000.14  ----    1  fsc 0x0001.00000000

以上是摘录的数据块头dump出来的信息,从中我们可以看到ITL的信息,包含事务ID(xid)、Undo回滚段(Uba)、事务的状态(Flag)等信息。

当事务需要更新数据行,Oracle会通过事务列表检索需要锁定的数据行是否已经被其他会话锁定,如果是则新事务需要等待"enq: TX contention",直到前面的事务结束,释放已经被持有的数据行。

总结

这篇文章中给大家介绍了Oracle数据库中几种典型锁的概念及其实现。Oracle中的锁遵循最小粒度的原则,尽量将加锁粒度控制在最小范围,避免影响其他事务的操作。具体说来,Oracle锁有以下的特点:

  1. 加锁粒度尽可能小;
  2. 读永远不会阻塞写,写也永远不会阻塞写;
  3. 锁永远不会主动升级。

这些特性现在看来都很普通,市面上主流的关系型数据库基本都支持这些特性。但是倒退20年,这些都是Oracle碾压其他数据库的杀手锏,Oracle之所以能成为数据库领域的No. 1,除了合理的市场策略外,这些精细的设计也是帮助其统治市场非常重要的因素!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值