【数据库】并发控制

数据库是一个共享资源,可以供多个用户共享使用.
以事务为单位管理用户程序的并发访问,提高资源共享效率。数据并发性意味着多个用户可以同时访问数据。
并发访问存在冲突吗?如何控制?

事务:用户定义的一个数据库操作序列.这些操作要么全做要么全不做,是一个不可分割的工作单元.

事务具有ACID
原子性(atomicity):数据库的逻辑单位,要么都做,要么都不做
一致性(consistency):一致性指的就是当数据库只从某种一致性状态到另一种一致性状态,数据库只包含成功事务提交的结果。
隔离性(Isolation):一个事务执行不受其他事务干扰。
持续性(Durability):一个事务一旦提交,它对数据库中的数据的改变就是永久的。

并发冲突带来的不一致性:

(1)丢失修改:两个事务同时读取同一数据并修改,结果后提交事务提交的修改覆盖了前提交事务提交的修改,导致前一个事务的修改丢失;效果等同于串行执行事务.

  • 关键:事务要识别提交修改时刻库中的数据是不是与原读取时刻库中的数据一致.

(2)不可重复读:同一事务前后两次读取的数据不一致

  • 前后两次读取同一数据,数据不一致
  • 前后两次按照同一条件读取数据,所得记录个数不同——-幻读
    (3)脏读
    读脏数据:一个事务读取了另一个事务未提交的数据.

数据不一致的主要原因并发操作破坏了事务的隔离性.
两面性:完全隔离事务就成了串行调度事务,系统并发访问的性能很低。我们适当较低事务的隔离性,允许某些不一致,实时采取补救措施

并发控制:

并发控制中主要技术有:封锁locking、时间戳timestamp、乐观控制法optimistic scheduler、多版本并发控制multi-version concurrency control
单处理机中,事务并行执行实际上就是交叉这并发执行

封锁协议:
1.一级封锁协议,事物T在修改数据R之前要加上排他锁,直到事物结束时才释放。
2.二级封锁协议,在一级封锁协议的基础上增加事务T在读取数据R之前要必须先对其增加S锁,读完后即可释放S锁
3.三级封锁协议,在一级封锁协议的基础上增加事务T在读物数据R之之前必须先对其加上S锁,直到事务结束才释放
活锁与死锁
避免活锁的简单方法是先来先服务(避免饿死的现象)
避免死锁:1)一次封锁法
2)顺序封锁
事务两段锁协议:
第一阶段获得封锁协议
第二阶段就是释放封锁
事务遵循两段锁是事务可串行化的充分条件,但是不必要;

封锁的粒度

封锁粒度越大,系统开销越大,性能越低;
多种粒度封锁
     多粒度封锁意味着允许多粒度树中的每一个结点的都被独立的加锁。
而对每一个结点加锁,同时也表明这个结点所有的后裔结点也被加一同类型的锁。

意向锁(这个部分了解的还很少,后期总结)



ORACLE的锁为行级锁,封锁粒度小。
Oracle的读与写互不阻塞
READ COMMITTED隔离级别可提供更多的并发性,但会给某些事务带来幻读和不可重复读的风险。
事务吞吐量 大,要求响应时间短
事务吞吐量不大,幻读和不可重复读导致不正确结果的风险低。
本级别不需要用户检测锁冲突

扫描二维码关注公众号,回复: 1325618 查看本文章

SERIALIZABLE

  • 修改行数少的短事务
  • 以读为主的长事务
  • 其它公司的系统在实现本隔离级别时会锁定读、写的快,但Oracle提供非阻塞的读和细粒度的行级锁,较少了读与写的竞争,故在串行级别仍然提供很好的吞吐量。
  • 本级别需要用户处理潜在的冲突–Cannot serialize access error 。

事务吞吐量大,修改机会多的事务
采用READ COMMITTED隔离级别;
采用合适的并发控制措施:悲观锁、乐观锁

悲观锁定:对并发冲突采取一种悲观的态度,认为相关业务的并发冲突度高,把本事务拟将更新的行在查询时就加锁,从而阻止其它事务更新这些行;
在SELECT语句后加上FOR UPDATE
也可以在select子句后用for update nowait
用在C/S架构
乐观锁定:对并发冲突采取乐观的态度,认为事务并发冲突度不高,对拟将更新的记录在查询时不锁定,在真正更新时再作检查,检查通过后方锁定。

【说明】悲观的锁定可以让用户知道他们的数据更新不会被阻塞,当他们更细数据库中的数据是不会遇到冲突
乐观锁定则会带来倘若用户执行数据更新操作时,如果数据在操作执行已经改变了,系统就必须通知终端用户操作不能执行。

实施乐观锁的方法

①更新前在应用中存储所要操作行的“前映像”,更新时对操作行的当前值与存储的旧记录进行比对,如果数据一致则表明没有并发冲突,就提交更新;否则则需要根据业务逻辑来作进一步处理。
Selec col1,col2 from table into old_col1,old_col2
where primary_key =primary_key
Update table
Set col1 = :new_col1, col2 = :new_col2, ….
Where primary_key = primary_key
And col1 = :old_col1
And col2 = :old_col2
If SQL% NOTFOUND
//更新失败,进一步处理
END IF

②在基表上使用一个能够追踪数据版本的特殊列。

  • 添加一个版本控制的字段
  • 添加一个时间戳字段,每次需要写入时对比时间戳;并发事务在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳的一致性,如果一致则就提交,否则就回滚放弃 。

③使用ORA_ROWSCN伪列。
在用CREATE TABLE创建基表时附加 ROWDEPENDENCIES参数,则Oracle在每次提交数据时都会依据系统时钟SCN创建该行的ORA_ROWSCN伪列〔缺省情况下Oracle在块级别维护SCN,为同一块上的多行共享一个SCN〕。
在提交数据修改时,将当前的ORA_ROWSCN与修改数据前得到的ORA_ROWSCN进行比较,如果一致说明数据没有被其他事务更新,则提交;否则说明数据已经被其它事务更新,则回滚,放弃修改。

create table dept (
Deptno char(4), 
dname varchar2(50), 
loc varchar2(50), 
Data number(7,2), 
constraint dept_pk primary key(deptno) 
) ROWDEPENDENCIES   
select ORA_ROWSCN from scott.dept where deptno=‘0001’;

【离线乐观锁和离线悲观锁】

课上彭老师的案列

考虑一个订单修改的例子
查询数据库,结果返回给表示层显示;
用户编辑数据〔可能花费较长时间〕
提交修改数据
由于整个业务持续的时间较长,如果把上述三步操作放在一个事务中处理,就会出现所谓的长事务,影响系统的吞吐量
一个处理办法是把业务分为下述三步,用两个事务处理:
事务1:查询数据库,结果返回给表示层显示;
用户编辑数据〔可能花费较长时间〕
事务2:提交修改数据
上述跨多个事务〔获取订单用一个事务,更新订单用另一个事务〕并发业务,传统的串行化隔离级别、乐观锁、悲观所均失效,需要使用所谓:
离线乐观锁
离线悲观锁

离线乐观锁实现方案
查询时把版本号存放到一个Sesion这样的容器中,更新时把当前版本号与Session中的版本号作对比。
与乐观锁的方案差不多。
适用于如下情况:
数据读出和数据更新在两个事务中进行;
并发冲突的可能性小,〔事务回滚导致的〕重做修改的代价小。

猜你喜欢

转载自blog.csdn.net/alearn_/article/details/80534897