王志国 周笑宇* 苏仕斌
(1.中国人民解放军联勤保障部队第九六三医院 黑龙江省佳木斯市 154002)
(2.中国人民解放军总医院医学创新研究部 北京市 100853)
数据库是多个用户使用的共享资源。 当多个用户同时访问同一数据库时,多个事务将同时访问数据库中的相同数据,此时,多个用户很有可能共同进行对于该数据库的查询、修改等操作。如果对于并发操作不加以管理,则可能会读写错误的数据,并破坏数据库四大特性中的一致性。 锁是数据库对并发访问的、在指定权限下可以进行共同访问的管理的机制,它用于保护以合理的方式被修改的数据。 当事务提交或者回滚后,对于该单位进行释放资源,此时其他用户可以正常的更新数据。
Oracle 关于锁的机制是轻量级的,它直接将锁作为数据块的属性,存储在数据块的头中,而不是建立用于数据锁管理的锁列表。锁是数据块本身的一个属性,每个数据块本身将数据的信息存储在自己的数据块中。这个地方叫做ITL。每当此数据块上有活动事务时,它的信息都会记录在该数据块中,以供后续操作查询,确保事务的一致性。在oracle 数据库中,没有实际属于对象或数据的锁。锁的信息是物理属性的一部分,而不是逻辑属性。
2.1.1 DML 锁DML 锁(数据锁),用于确保数据库数据的完全度,其中包括行级锁(TX 锁)、表级锁(TM 锁)。
2.1.2 DDL 锁
DDL锁(数据字典锁),用于保护数据库创建的对象的完整组成,如视图、索引、存储过程等。其中包排他DDL 锁(Exclusive DDL lock)、共享DDL 锁(Share DDL lock)、可中断解析锁(Breakable parse locks)。
2.2.1 行级锁
行级锁指对正在被修改的行进行锁定,其他用户可以其他没有被锁定的数据行。当数据库执行删除、更新等操作时,会自动将该事务在表中的操作行上获得独占锁。当事务启动第一次修改时,它将获得TX锁(事务锁),并将保持该锁,直到事务被提交(COMMIT)或被回滚(ROLLBACK)。
在使用以下语句时,Oracle 会自动应用行级锁:insert, update, delete, select…for update
2.2.2 表级锁
表级锁用于确保表的结构在其他事务对于进行该表操作时不会改变。当用户对表执行 DDL 或 DML 操作时,oracle 数据库会自动给该表加一个表级锁。当行级锁被事务操作后,事务会自动继承该行级锁上级的表级锁(共享锁), 以防止其他事务生成的DDL 语句,通过DDL 操作,来影响记录行的更新。
图1:同时用两个账户对同一条数据执行更新
图2:查询当前的TX 锁和TM 锁
图3:查询object_id
图4:scott 用户进行回滚操作
图5:scott 用户回滚后查询v$lock 视图
表级锁按照锁定的强度,可以分为以下的五个类型:
(1)行共享:禁止其他的排他锁对表进行锁定;
(2)行排他:禁止使用除该排他锁之外的其他共享锁、排他锁;
图6:system 用户进行回滚操作
图7:system 用户回滚后查询v$lock 视图
(3)共享锁:对当前操作的表进行锁定,仅允许非该用户的其他用户进行查询操作,并且禁止其他用户插入、更新和删除行;
在出现以下条件之一时,共享锁就会被当前所持有的事务进行释放:
A、当事务被ROLLBACK 或COMMIT。
B、数据库被完全退出。
C、程序被强行关闭或停止。
(4)共享排他锁:禁止使用共享锁,以及限制级别更高的的数据锁;
(5)排他锁:数据库中所有锁中对于权限限制最高的锁,仅允许非当前用户查询该表的行。禁止修改和锁定表。
分别采用两个不同的账号登录同一数据库,对oracle 数据库中自带的scott 用户下的emp 表进行更新操作,如图1 所示。
可以很明显的看出,此时第二个账户并没有进行update 操作,这是因为执行update 命令时,oracle 数据库会自动将该行数据添加行级锁,使其他用户无法更新该行数据。为了验证该说法,此时查询v$lock 视图,如图2 所示。
可以很明显的看出,v$lock 视图中,添加了关于该行语句的TX 锁和TM 锁,为了进一步确认上述锁是由emp 表所产生的,我们通过查询dba_objects 表来进行进一步的验证,如图3 所示。
明显看到,该进程ID 号是由于对于dba_object 表进行加锁所产生的,为了进一步验证说法,此时对两个事务均进行回滚操作,如图4 所示。
此时可以很明显的看出,当第一个用户中的update 被回滚后,TX 锁瞬间被解锁,资源被释放,才使得第二个用户的update 得以成功执行,此时查看一下v$lock 视图,可以发现,此时TX 锁的ID 号已经改变,且原来的ID 号已经消失,如图5 所示。
如图6 所示。
此时查询v$lock 视图,可以很明显的发现,所有的TX 锁和TM 锁均已经消失,如图7 所示。
死锁是指在两个或多个进程执行过程中,由于资源竞争或通信原因而导致的阻塞现象。如果没有外力,只能互相等待分配资源,所有的进程都无法继续运行。
在两个或多个任务中,如果每个任务都互相对于其他已经被试图锁定的任务进行了加锁的操作,此时这些任务会互相等待对方进行资源分配,造成互相之间影响的阻塞现象,因此产生了死锁。例如:事务A 对于行 1 的共享锁进行获取。事务 B 对于行 2 的共享锁进行获取。此时事务A,因为等待事务B 完成并释放事务A 对于行2现有的共享锁,从而产生了排他锁,此时事务B,因为等待事务A完成并释放事务B 对于行2 现有的共享锁,从而也产生了排他锁。但是由于事务A 等待事务B 完成之后才能进行释放该行级锁,事务B 也在等待事务A 完成之后才能释放该行级锁,两个事务互相阻塞,产生了闭环,所以造成了死锁。这就是死锁中的循环依赖:事务 A 和事务B 之间相互依赖,从而产生了闭环,导致了死锁的产生。
根据Oracle 数据库的机制,该数据库会自动发现数据库中产生的死锁,并通过破坏死锁条件之一进行释放部分资源,从而对某个事务的全部锁进行释放,或直接消除代价最小的事务,从而可以使其他单位的事务继续运行。
(1)在UPDATE 和DELETE 后,应及时的进行COMMIT 或ROLLBACK 操作,以免表或行的锁定时间过长,造成死锁。
(2)当多个事务同时对于某个数据表进行操作时,此时不要使用共享锁。相反,请使用共享更新锁,以便其他用户可以使用行级锁来提高并行性。
(3)在高应用程序负载期间不适合修改相关的数据结构(表,视图,触发器,存储过程等)。
Oracle 数据库之所以能成为全世界最广泛使用的数据库之一,原因之一就是因为它对于事务的原子性、一致性、隔离性、持久性的保障,而锁是oracle 数据库为了确保数据的一致性、实现并发控制的非常重要的技术。在日常oracle 数据库的维护和开发中,要深刻理解锁的概念,并确保开发过程中不要出现死锁,这样才能确保数据更加健壮、稳定。