浅谈数据库的锁技术

2023-02-13 版权声明 我要投稿

数据库 (Database) 是按照数据结构来组织、存储和管理数据的仓库, 它是一个多用户使用的共享资源。当多个用户并发地存取数据时, 在数据库中就会产生多用户多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据, 破坏数据库的一致性。为保证数据一致性, 数据库引入锁技术。

数据库就是通过锁技术机制来解决并发问题, 当事务对某个数据对象进行操作前, 先向系统发出请求, 并对其加锁, 加锁后事务就对该数据对象有了一定的控制, 在该事务释放锁之前, 其他的事务不能对此数据对象进行更新操作, 从而保证了数据一致性。下面对目前主流数据库 (INFORMIX, ORACLE, SQLSE RVER等) 锁技术做个简单介绍。

在ORACLE数据库中有两种基本的锁类型, 即:排它锁 (Exclusive Locks, 即X锁) 和共享锁 (Share Locks, 即S锁) 。加了共享锁的数据对象可以被其他事务读取, 但不能修改。当数据对象被加上排它锁时, 其他的事务不能对它读取和修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。

在informix数据库中锁的类型分为SHARED锁、EXCLUSIVE锁和PROMOTABLE锁。SHARED锁只保留对象的可读性。当锁存在时, 对象不能改变。多个程序可对同个对象加SHARED锁。EXCLUSIVE锁只能使单个程序使用。在程序要改变对象时使用。当其他锁存在时, EXCLUSIVE锁不能使用。当使用了EXCLUSIVE锁后, 其他锁不能用于同一对象。PROMOTABLE锁实现更新的目的。PROMOTABLE锁可以放在已经有SHARED锁的记录, 但不能放在已经有PROMOTABLE锁和EXCLUSIVE锁的地方。当记录上无其他锁 (含SHARED锁) 情况下, 这时在程序准备改变锁的记录时, PROMOTABLE锁可以提升为EXCLUSIVE锁。如果在已有SHARED锁的记录上设置了PROMOTABLE锁, 在PROMOTABLE锁可以提升到EXCLUSIVE锁之前需要删除SHARED锁。PROMOTABLE锁只能在INFORMIX Universal Server中支持。

在SQL SERVER数据库中锁的类型分为共享锁、更新锁、排他锁, 意向锁、计划锁。共享锁是两个或多个锁是可以同时存在于同一资源上的 (比如同一个表上) , 共享锁允许其它session同时读资源。更新锁可以解决死锁, 更新锁的意思是:“我现在只想读, 你们别人也可以读, 但我将来可能会做更新操作, 我已经获取了从共享锁 (用来读) 到排他锁 (用来更新) 的资格”。一个事物只能有一个更新锁获此资格。排他锁即独占锁, 其它事务既不能读, 又不能改排他锁锁定的资源, 是被独占的。意向锁就是说在屋 (比如代表一个表) 门口设置一个标识, 说明屋子里有人 (比如代表某些记录) 被锁住了。另一个人想知道屋子里是否有人被锁, 不用进屋子里一个一个的去查, 直接看门口标识就行了。计划锁不允许任何其它session连接该表。

为保护不同的数据对象, 数据库锁范围一般分为数据库锁、表级锁、记录级锁。数据库锁用于保护数据库的内部结构, 表级锁用于保护数据库对象的结构, 如表、索引等的结构定义, 数据锁用于保护数据的完整性。

在oracle数据库中, 系统自动在所要操作的表上申请表级锁。当表级锁获得后, 系统再自动申请行级锁, 并将实际锁定的数据行的锁标志位进行置位。这样在事务加锁前检查行级锁相容性时就不用再逐行检查锁标志, 而只需检查表级锁模式的相容性即可, 大大提高了系统的效率。表级锁包括了行级共享锁、行级排它锁、共享锁、排它锁等多种模式, 不同的SQL操作产生不同类型的表级锁。

在informix数据库中, 程序控制数据库级锁的时期, 数据库关闭时, 数据库锁级也就释。表级、记录级、索引级锁的时期依赖于使用的SQL语句以及是否使用事务。如果数据库没有使用事务, 也就是说, 事务日志不存在并且你没有使用COMMIT WORK语句, 当运行UNLOCK TABLE语句时, 表级锁就释放。当使用了事务时, 一旦事务结束, 表级、记录级、索引级锁都释放。

在执行数据库select语句的时候需要给操作对象 (表或者一些记录) 加上共享锁, 但加锁之前需要检查是否有排他锁, 如果没有, 则可以加共享锁 (一个对象上可以加n个共享锁) , 否则不行。共享锁通常在执行完select语句之后被释放, 当然也有可能是在事务结束 (包括正常结束和异常结束) 的时候被释放, 主要取决与数据库所设置的事务隔离级别。

排他锁的时期依赖于是否使用事务。如果没有使用事务, 被修改的记录写到磁盘上就会释放该锁。当使用了事务时, 锁就会保持到事务的结束。这个动作防止其他程序使用可能回滚到原来状态的记录。在执行insert、update、delete语句的时候需要给操作的对象加排他锁 (我感觉在执行insert的时候应该是在表级加排他锁) , 在加排他锁之前必须确认该对象上没有其他任何锁, 一旦加上排他锁之后, 就不能再给这个对象加其他任何锁。排他锁的释放通常是在事务结束的时候 (当然也有例外, 就是在数据库事务隔离级别被设置成Read Uncommitted (读未提交数据) 的时候, 这种情况下排他锁会在执行完更新操作之后就释放, 而不是在事务结束的时候) 。数据库是支持在一个事务中进行自动锁升级的, 例如, 在某个事务中先执行select语句, 后执行update语句, 这两条语句操作了同一个对象, 并且假定共享锁是在事务结束的时候被释放的。如果数据库不支持自动锁升级, 那么当update语句请求排他锁的时候将不能成功。因为之前select语句的共享锁没有被释放, 那么事务就进入了无限等待, 即死锁。有了自动锁升级, 在执行update语句的时候就可以将之前加的共享锁升级为排他锁, 但有个前提, 就是这个共享锁必须是本事务自己加的, 而且在操作对象上没有在加其他任何锁, 否则共享锁是不能被升级为排他锁的, 必须等待其他锁的释放。

当程序要提取或修改一个上锁的记录时, 会有下面几种情况:

1、数据库马上通过SQLCODE变量或SQLSTATE结构给程序返回一个错误代码。

2、在数据解锁前, 数据库将程序挂起。

3、数据库将程序挂起一段时间。如果锁还未解, 数据库给程序返回一个错误代码。你可以通过SET LOCK MODE模式选择以上结果。如果你喜欢程序等待 (对大多数程序而言这是最好的选择) , 运行下列语句:SET LOCK MODE TO WAIT。当设置了锁模式后, 程序常忽视其他并发程序的存在性。如果程序需要访问其他程序已上锁的记录时, 它等待别的程序解锁, 然后继续。延迟的时间常不可预测。

等待解锁不利的一面就是可能会等待很长时间。如果不能接受很长延迟, 程序可以运行下列语句:SET LOCK MODE TO NOT WAIT选择不等待。当程序需要一个锁记录时, 它马上返回一个错误代码, 且当前的SQL语句终止。这时, 程序必须回滚当前的交易再试一次。程序开始时, 数据库初始设置为不等待。当你使用UNIVERSAL SERVER时, 你有另外的选择。你可以让数据库设置等待时间的上限。你可用下列语句:SETLOCK MODE TO WAIT 18让数据库有18秒等待上限。若期间锁还没有解开, 将返回错误代码。

通常在执行更新操作的时候要先查询, 也就是我们通常会在update语句和delete语句中加where子句。那么, 有的数据库系统可能会在执行查询的时候先给操作对象加共享锁, 然后在更新的时候加排他锁, 但这么做会有问题, 也就是如果两个事务同时要更新一个对象, 都先给这个对象加了共享锁, 当要更新的时候, 都请求升级锁, 但由于这个对象上存在对方事务加的共享锁, 所以无法升级。这样两个事务就在等待对方释放共享锁, 进入死锁状态, 死锁是程序之间相互阻塞, 每个程序在其他程序要访问的对象上设置了锁。每个数据库服务器都设置锁等待的上限。如果超时, 数据库服务器就认为发生了死锁且返回相关的错误代码。数据库管理员可以设置和修改等待时间的上限。

更新锁就是为了解决死锁这个问题, 即在执行查询操作的时候加的不是共享锁而是更新锁 (一个对象上只能有一个更新锁和n个共享锁) , 当要更新的时候, 再将更新锁升级为排他锁, 升级前提是这个对象上只有本事务加的更新锁, 没有其他任何锁了。当然如果在执行查询的时候就给事务加排他锁也能解决死锁问题, 但这样似乎会减弱系统的并发性能。

通过上面对数据库锁的了解, 可以根据具体业务情况综合使用锁的类型和范围, 在建表或者读取更新数据的时候, 既保证数据一致性又减少并发等待。

摘要:本文主要介绍了数据库锁的类型, 锁的范围及锁的时期, 数据库如何利用锁技术保证数据的一致性。

关键词:数据库,一致性,锁

上一篇:东南亚手足口病爆发流行对未来强震的指示意义下一篇:浅论高校办公室秘书工作的规范性