数据库事务,事务并发问题,锁的隔离级别,锁的粒度(二)
上一篇文章描述了 事务并发会遇到的问题,为了解决这些文件,所以就有了封锁。
数据库锁
自考教材对 ‘封锁’ 的概念:
封锁是最常用的并发控制技术 基本思想:需要时,事务通过向系统请求对它所希望的数据对象加锁,以确保它不被非预期改变。
一个锁实质上就是允许或阻止一个事务对一个数据对象的存取特权。
锁的类型
1、排他锁(X锁),用于写操作(更新操作会申请一个排他锁)
2、共享锁(S锁),用于读操作(读取操作会申请一个共享锁)
用封锁进行并发控制 封锁的工作原理:
1.若事务T对数据D加了X锁,则所有别的事务对数据D的锁请求都必须等待直到事务T释放锁。
2.若事务T对数据D加了S锁,则别的事务还可对数据D请求S锁,而对数据D的X锁请求必须等待直到事务T释放锁。
3.事务执行数据库操作时都要先请求相应的锁,即对读请求S锁,对更新请求X锁。这个过程一般是由DBMS在执行操作时自动隐含地进行。
4.事务一直占有获得的锁直到结束时释放。
自考教材只列出了这两种锁,但是查阅资料我们知道,其实还有第三种锁,就是
3、更新锁(U锁):用来预定要对此对象施加X锁,它允许其他事务读,但不允许再施加U锁或X锁;可以理解为到X锁中间的一个过渡锁。
更新锁 主要作用是防止死锁,当多个事务对同一数据都有共享锁(S锁),当同时有两个(或以上)事务要进行写操作时,这些事务都要将共享(S)锁升级为排它锁(X),但是它们都不会释放共享锁而是一直等待对方释放,这样就造成了死锁。
所以要引入更新锁,当事务要进行写操作时,先申请更新锁(U),因为更新锁(U)可以和其它共享锁(S)可以共存,所以不需要等待其它事务共享锁(S)的释放;而更新锁一旦申请成功后,就不允许再有其它的更新锁或排它锁。这就保证了数据在同一时刻只有一个更新锁,在数据修改的时候再升级为排它锁,就可以避免死锁。
封锁的粒度
我们通常以粒度来描述封锁的数据单元的大小
DBMS可以决定不同粒度的锁 ,粒度越细,并发性就越大,但软件复杂性和系统开销也就越大。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。对整个表加锁,影响标准的所有记录。通常用在DDL语句中,如DELETE TABLE,ALTER TABLE等。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。对一行记录加锁,只影响一条记录。通常用在DML语句中,如INSERT, UPDATE, DELETE等。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。数据库引擎通常必须获取多级别上的锁才能完整地保护资源。
这三个锁粒度是从网上搜索的资料,暂时可以这样理解,其中具体的实现原理还需要进一步了解。
死锁和活锁
活锁是由于优先级高的事务一直被优先调度,导致优先级别低的事务一直处于等待状态而不能被处理,活锁问题的解决与算法调度有关系,最简单的方法就是 “先来先服务”。
死锁是指线程由于某种原因一直等待而发生的一种僵死状态。简单的说,进程A等待进程B释放他的资源,B又等待A释放他的资源,这样就互相等待就形成死锁。
死锁产生条件:
(1) 一次性锁请求
每一次事务处理时,一次提出所有的锁请求,仅当这些请求全部满足时才进行事务处理,否则让其等待。这样虽然不会出现死锁但是系统性能会降低很多。
(2) 锁请求排序
将每个数据单元标记以线性顺序,然后要求每一个事物按照此顺序提出锁请求。这种方式降低了死锁现象,但是同时也降低了并发性。
(3) 序列化处理
通过应用设计为每一数据单元建立一个“主人”程序,对给定的数据单元的所有请求都发送给“主人”,而主人以单道的形式运行,这样系统可以是多道运行,但由于任何两道都不请求相同的数据单元,因为可避免死锁的现象发生,但系统性能,数据完整性可能受到影响。
(4) 资源剥夺
每当事务因锁请求不能满足受阻时,强行令两个冲突的事务中的一个rollback,释放所有的锁,然后再重新运行。使用这种方法需要注意活锁问题。
从网上查询资料,关于锁还有另一种说法:悲观锁和乐观锁。
悲观锁,指的就是上面提高的数据库的锁(排它锁,共享锁,更新锁等由DBMS来实现的锁)
乐观锁:一般是指用户自己实现的一种锁机制,并不是真实存在的锁。它对于数据被外界修改持乐观态度,认为数据不会修改,所以数据处理时数据库不再为其加锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。可以在数据表中添加一个冗余字段,比如时间戳,在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。比如更新数据时,拿着之前相同的查询条件再一次查询数据库,若仍能够得到数据证明此条记录无人修改,即可继续操作,否则表示当前有用户正在抢夺资源,就放弃更新操作。
乐观锁实现方式:
a. 版本号(记为version):在表中新增一个version字段,作为版本标识的记号,当数据每次更新时就将此字段加1,每次读取数据时一并将version字段读出,更新数据之前比较version字段值。举个栗子,若此次读取的 新version值 比 旧version值 大,说明有其他事务在此之前修改过这条记录,并为版本号字段增加了数量,此时就无法得到这条记录,需要重新开始一遍。此字段存在的意义是作为一个标志位,准备修改数据时将version读出,真正修改数据前再查询一次version,比较上一次得到的version值和现在version是否一致,相同继续操作,不同重新开始。可使用类似 update … where … and version=”old version” 语句进行比较。根据返回结果是否为0执行下一步的操作。
b. 时间戳(timestamp):和版本号基本一样,只是通过时间戳来判断而已,注意时间戳要使用数据库服务器的时间戳,而不能是业务系统的时间。
c. 待更新字段:和版本号方式相似,只是不增加额外字段,直接使用表中现有做版本控制信息的标志位,因为有时我们可能无法改变旧系统的数据库表结构。假设现在需要保存一个订单记录,有库存stock字段: 首先需要查询数据库,得到这个商品的库存数量,再判断库存数量是否大于用户购买数量,经历一系列判断逻辑都能够通过的话,保存这个订单数据之前,需要拿着当初查询数据库时的库存字段再查询一次这个商品,若通过原始库存值能够得到商品对象,那么就进行订单的修改操作,否则就是别的用户正在抢夺资源,应放弃操作重新再来。
d. 所有字段:和待更新字段类似,只是使用所有字段做版本控制信息,只有所有字段都没有变化才会执行更新。
posted on 2021-05-24 15:09 IT_xiaozhang 阅读(208) 评论(0) 编辑 收藏 举报