数据库事务、锁

事务特征

第1、原子性,即不可分割性;
第2、一致性,保证数据更新先后与业务的一致性;
第3、隔离性,多个事务不相影响;
第4、持久性,事务提交后数据将永久存储杂数据库。html

隔离级别

读未提交
一个事务在修改或新增后,另外一个事务能够读取上个事务未提交的数据,会产生“脏读”。
读提交(oracle默认隔离级别)
一个事务在修改或新增后,若是未提交,另一个事务不能去读。只能读取已经提交事务的数据。可能出现不可重复读,如连续读取两次,会形成第一次读的数据,在第二次读的数据中或多或少。
重复读(MySQL的默认事务隔离级别)
一个事务在读取过程当中, 另外一个事务不能写入。可能会形成幻读。
序列化
对于每行,一个事务在读取过程当中,另外一个事务不能读取或写入。web

脏读、不可重复读、幻读含义

脏读:一个事务能够读物另外一个事务未提交的数据。sql

不可重复读:在一个事务中不一样时间段查询出现不一样的结果,可能被更新可能被删除。数据库

幻读:在一个事务中不一样时间段查询,记录数不一样。与不可重复读的区别是:在幻读中,已经读取的数据不会改变,只是与之前相比,会有更多的数据知足查询条件。session

Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别并发

数据库大并发操做要考虑死锁和锁的性能问题。
在数据库系统的ACID特性中,隔离性是指数据库系统必须具备隔离并发运行的各个事务的能力,使它们不会相互影响。
数据库采用锁来实现事务的隔离性。锁的基本原理以下:
一、当一个事务访问某种数据库资源时,若是执行select语句必须先得到共享锁,若是执行insert、update、或delete语句,必须先得到独占锁,这些锁用于锁定被操做的资源。
二、当第二个事务也要访问相同的资源时,相应的语句也必须先得到相应的锁,此时就能够根据状况来决定第二个事务是等待锁,仍是能够当即得到锁。oracle

锁的基本种类

行级锁

行级锁是一种排他锁,防止其余事务修改此行;在使用如下语句时,Oracle会自动应用行级锁:svg

INSERTUPDATEDELETESELECTFOR UPDATE [OF columns] [WAIT n | NOWAIT];

SELECT … FOR UPDATE语句容许用户一次锁定多条记录进行更新
使用COMMIT或ROLLBACK语句释放锁性能

表级锁

  • 共享锁(Shared lock)

锁定表,对记录只读不写,多个用户能够同时在同一个表上应用此锁spa

T1:  select * from table
T2:  update table set column1='hello'

过程: T1运行 (加共享锁) T2运行 If T1 还没执行完 T2等...... else 锁被释放 T2执行 endif

T2之因此要等,是由于T2在执行update前,试图对table表加一个排他锁,而数据库规定同一资源上不能同时共存共享锁和排他锁。因此T2必须等T1执行完,释放了共享锁,才能加上排他锁,而后才能开始执行update语句。

T1:    select * from table T2: select * from table 这里T2不用等待T1执行完,而是能够立刻执行。 分析: T1运行,则table被加锁,好比叫lockA T2运行,再对table加一个共享锁,好比叫lockB。

两个锁是能够同时存在于同一资源上的(好比同一个表上)。这被称为共
享锁与共享锁兼容。这意味着共享锁不阻止其它session同时读资源,但阻
止其它session update

T1:    select * from table T2: select * from table T3: update table set column1='hello'

此次,T2不用等T1运行完就能运行,T3却要等T1和T2都运行完才能运行。由于T3必须等T1和T2的共享锁所有释放才能进行加排他锁而后执行update操做。

  • 更新锁
    由于共享锁会引起死锁,因此引入更新锁
T1:
begin tran select * from table(updlock) (加更新锁) update table set column1='hello' T2: begin tran select * from table(updlock) update table set column1='world'

更新锁的意思是:“我如今只想读,大家别人也能够读,但我未来可能会作更新操做,我已经获取了从共享锁(用来读)到排他锁(用来更新)的资格”。一个事务只能有一个更新锁获此资格。

T1执行select,加更新锁。
T2运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。

当后来有user三、user4…须要查询table表中的数据时,并不会由于T1的select在执行就被阻塞,照样能查询。
更新锁是能够和共享锁共存的。

  • 排他锁(独占锁,Exclusive Locks)

其它事务既不能读,又不能改排他锁锁定的资源。

T1:    update table set column1='hello' where id<1000 T2: update table set column1='world' where id>1000 假设T1先达,T2随后至,这个过程当中T1会对id<1000的记录施加排他锁.但不会阻塞T2的update。 假设id都是自增加且连续的 T1: update table set column1='hello' where id<1000 T2: update table set column1='world' where id>900

T1先达,T2马上也到,T1加的排他锁会阻塞T2的update。

  • 意向锁(Intent Locks)

意向锁就是说在屋(好比表明一个表)门口设置一个标识,说明屋子里有人(好比表明某些记录)被锁住了。另外一我的想知道屋子里是否有人被锁,不用进屋子里一个一个的去查,直接看门口标识就好了。

当一个表中的某一行被加上排他锁后,该表就不能再被加表锁。数据库程序如何知道该表不能被加表锁?一种方式是逐条的判断该表的每一条记录是否已经有排他锁,另外一种方式是直接在表这一层级检查表自己是否有意向锁,不须要逐条判断。显而后者效率高。

T1:    begin tran select * from table (xlock) where id=10 --意思是对id=10这一行强加排他锁 T2: begin tran select * from table (tablock) --意思是要加表级锁

假设T1先执行,T2后执行,T2执行时,欲加表锁,为判断是否能够加表锁,数据库系统要逐条判断table表每行记录是否已有排他锁,
若是发现其中一行已经有排他锁了,就不容许再加表锁了。只是这样逐条判断效率过低了。

实际上,数据库系统不是这样工做的。当T1的select执行时,系统对表table的id=10的这一行加了排他锁,还同时悄悄的对整个表
加了意向排他锁(IX),当T2执行表锁时,只须要看到这个表已经有意向排他锁存在,就直接等待,而不须要逐条检查资源了。

T1:    begin tran update table set column1='hello' where id=1 T2: begin tran update table set column1='world' where id=1

这个例子和上面的例子实际效果相同,T1执行,系统对table同时对行家排他锁、对页加意向排他锁、对表加意向排他锁。

  • 计划锁(Schema Locks)

alter table …. (加schema locks,称之为Schema modification (Sch-M) locks

DDL语句都会加Sch-M锁
该锁不容许任何其它session链接该表。连都连不了这个表了,固然更不用说想对该表执行什么sql语句了。

用jdbc向数据库发送了一条新的sql语句,数据库要先对之进行编译,在编译期间,也会加锁,称之为:Schema stability (Sch-S) locks

select * from tableA

编译这条语句过程当中,其它session能够对表tableA作任何操做(update,delete,加排他锁等等),但不能作DDL(好比alter table)操做。

乐观锁与悲观锁

这两种锁是应用中实现的,数据库底层不带这两种功能,主要为了提升程序并发性。

  • 悲观锁

利用数据库自己的锁机制实现。经过上面对数据库锁的了解,能够根据具体业务状况综合使用事务隔离级别与合理的手工指定锁的方式好比下降锁的粒度等减小并发等待。

  • 乐观锁

利用程序处理并发。原理都比较好理解,基本一看即懂。方式大概有如下3种
对记录加版本号
对记录加时间戳
对将要更新的数据进行提早读取、过后对比。

所谓悲观锁就是基于数据库机制实现的。好比在在使用select子句的时候加上for update,那么直到改子句的事务结束为止,任何应用都没法修改select出来的记录。

所谓乐观锁是基于应用的版本机制来实现的。通常会在表里面设计一个版本字段v(通常会把这个字段设为timestamp)。通常的update场景是这样:

select a, v from tb where id=1;   

假设获得数据是:['xxx', 11111]
update tb set a='yyyy', v=systimestamp where v=11111; 
//注意, v通常不会在业务操做的时候修改

这要求每一次update操做都变动版本字段,不然仍是要进程间的数据 仍是会被相互覆盖。

乐观锁没法锁定其余应用对数据的操做。
乐观锁与悲观锁