数据库(一)事务和隔离级别

数据库事务的四个特性(ACID)

1、原子性(Atomicity)

事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做

2、一致性(Consistency)

事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。

3、隔离性(Isolation)

一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

4、持续性(Durability)

也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

数据库事务的隔离级别

1、未提交读(Readuncommitted)

事务中的修改及时没提交也会被其他事务可见,这样会产生脏读,如果事务失败回滚,则其他事务之前的到的数据则是脏数据。从性能上讲,不会别别的事务提高太多,但是极其不安全。

2、读提交(Readcommitted)

又可叫不可重复读,大多数数据库默认的隔离模式(MySQL不是)。在事务完成提交之前,其他事务看不到该事务的修改结果。执行两次同样的查询可能看到不一样的结果。

3、重复读(Repeatableread)

事务A读取与搜索条件相匹配的若干行。事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。事务A再读取时,却发现数据发生了变化。造成了幻读。(MySQL默认的隔离级别)

4、序列化(Serializable)

Serializable是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。

其中1和4比较好理解,一个不安全,一个安全却性能较差。

对于2读提交(不可重复读)是针对一条记录,因为T1事务中读取一条记录时未加锁,此时T2事务可以修改该记录,当T1第一次读取后,并使用该值做了一些校验正准备在此读取做更新操作时,发现数据和之前第一次读的不一致了。例如,你在ATM取钱,先查询了一下余额发现还有10块钱,你正准备取出买东西时,10块钱却被你女朋友利用网上银行扣走了,在你查完余额转到取钱时,ATM发现余额不足了。前后两次你看到的余额不同,造成了不可重复读或者虚读。

对于3可重复读在一条记录上的操作上不能读取已由其它事务修改了但是未提交的行,其它任何事务也不能修改在当前事务完成之前由当前事务读取的数据。但是对于其它事务插入的新行数据,当前事务第二次访问表行时会检索这一新行。因此,这一个隔离级别的设置解决了Non-RepeatableReads不可重复读取的问题,但是避免不了PhantomReads幻读。例如:事务T1在读取R1和修改R2,此时T2不能够读取R2也不能修改R1,这样T2的操作就不会影响到T1的操作,但是,如果T1中含有一个统计某个范围内记录数量的操作,而T2在此时正好在此范围内插入了一条记录,则会草成T1的幻读,即第一次读此范围内一共2条数据,而在次读的时候却有了3条数据。

两者的的区别是读提交,在读取一条记录时会出现不可重复读;可重复读,通过对事务里面的读写操作加锁解决了读提交的问题,但是对统计某个范围内的记录数量,还是会产生幻读。

避免不可重复读需要锁行;避免幻影读则需要锁表,很多人容易搞混不可重复读和幻读,确实这两者有些相似。不可重复读重点在于updatedelete,而幻读的重点在于insert

数据库锁

对于解决可重复读需要锁行;对于解决幻影读则需要锁表。目前主要使用悲观锁和乐观锁来实现这两种隔离级别。可重复读,第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据;幻影读,读用读锁,写用写锁,读锁和写锁互斥,可有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。

1、悲观锁

对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。

悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据,来保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

2、乐观锁

乐观锁机制采取了更加宽松的加锁机制,在一定程度上解决了这个问题。

乐观锁大多是基于数据版本(Version)记录机制实现,为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。要说明的是,MVCC的实现没有固定的规范,每个数据库都会有不同的实现方式(这里讨论的是InnoDBMVCC)。

3、目前数据库的处理方式

目前MySQLORACLEPostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来避免这两种问题。