- http://www.jianshu.com/p/eb150b4f7ce0# 我理解的数据库事务!!~~
- http://www.jianshu.com/p/2b34dd65b2d3 数据库相关知识回顾与总结
进程、线程和事务
进程Process 线程Thread 事务Transanction 线程也是执行者,它与进程的区别就是,创建每个线程之前都必须事先有一个进程与它关联,而线程可以访问它关联着的进程所占有的资源。线程与进程的关系是工厂模式的一个实例,进程是线程的制造者,而被制造出的线程必须与它的制造者关联
数据库的事务与线程相似的地方
- 线程之间共享同一片资源,而事务共享的则是数据库内部的数据
- 多线程的意义在于并发执行,提高效率;事务并发执行也能提高程序与数据库交互的效率。
- 线程之间共享同一片资源,而事务共享的则是数据库内部的数据
- 数据库事务:
MySQL2002年才支持事务
只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初的状态
事务四个特性:ACID
- 原子性 :事务是应用中最小的执行单位
- 一致性
- 隔离性
事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。 同时,并行事务的修改必须与其他并行事务的修改相互独立。
事务的隔离性一般由事务的锁来进行控制
数据库需要保证每一个事务在它的修改全部完成之前,对其他的事务是不可见的。即不能让其他事务看到该事务的中间状态
- 持久性
事务结束后,事务处理的结果必须能够得到固化,即使系统出现各种异常也是如此。
数据库的隔离级别
由于性能的考虑,许多数据库允许使用牺牲隔离属性来换取并发度,从而获取性能的提升。
- 读写异常4种
- 脏读(dirty read):(修改时允许读取导致脏读)脏读是指在一个事务处理过程里读取到了另一个未提交的事务中的数据
- 不可重复读(non-repeatable read):读取时允许修改(Update)导致不可重复读
- 丢失修改(lost update):撤销覆盖、提交覆盖
- 幻读(phantom read):(读取时允许插入或删除导致幻读)
幻读和不可重复读有什么区别?
- 不可重复读的重点是修改:同样的条件,我们读取过的数据,再次读取出来发现值不一样了
- 幻读的重点在于新增或者删除:同样的条件,第一次和第二次读取出来的记录数不一样
但是如果从控制的角度来看,两者的区别就比较大了:
- 对于不可重复读,只需要锁住满足条件的记录
- 对于幻读,它是由于并发事务增加或者删除记录导致的,不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读
- 事务隔离级别(由低到高)
- 读未提交(Read uncommited)
只有一个事务可以同时写,多个事务可以同时读
避免了丢失修改,但是却可能出现脏读
- 读已提交(Read committed)
读取数据的事务允许其他事务继续访问该行数据(也可以修改),
但是未提交的写事务将会禁止其他事物访问该行。
读已提交是在读未提交的基础上避免了脏读,但是有不可重复读的问题
- 可重复读(Repeated read)
读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
可以通过“共享读锁”和“排他写锁”避免了不可重复读和脏读,但是有时可能出现幻读
- 序列化(Serializable)
提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个执行,但是不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
序列化是最高的事务隔离级别,同时代价也花费最高,但是性能很低,一般很少用。在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读
LU丢失修改 | DR脏读 | NRR不可重复读 | SLU 丢失修改2 | PR幻读 | |
---|---|---|---|---|---|
RU读未提交 | N | Y | Y | Y | Y |
RC读已提交 | N | N | Y | Y | Y |
RR可重复读 | N | N | N | N | Y |
S 序列化 | N | N | N | N | N |
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据系统的隔离级别设为Read Committed。它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、幻读等并发问题。但是在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
JDBC的事务支持
Connection默认打开自动提交
Savepoint
封锁
事务相关的锁
事务并发时对对象进行加锁
基本封锁类型有两种:排他锁(exclusive locks,简称X锁)和共享锁(share locks,简称S锁)
X锁(排他写锁):若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事物都不能再对A加任何类型的锁,直到T释放A上的锁为止。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A;‘
S锁(共享读锁):若事务T对数据A加上S锁,则事务T可以读A但是不能修改A,其他事务只能对A加S锁而不能加X锁,直到T释放A上的S锁为止。这就保证了其他食物可以读A,但在T释放A上的S锁之前不能对A进行任何修改。
活锁和死锁
由此可见数据库中不适合预防死锁,只适合进行死锁的诊断与解除
数据库管理系统的并发控制系统一旦检测到系统中存在死锁,就要设法解除。通常采用的方法是选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有的锁,使其他事务得以继续运行下去。
当然,对撤销的事务所进行的数据修改必须加以恢复。
两段锁协议
- 在对任何数据进行读、写操作之前,首先要申请并获得对该数据的封锁;
- 在释放一个锁之后,事务不再申请和获得任何其他封锁。
所谓两段锁的含义是,事务分为两个阶段:
第一个阶段是获得封锁,也称为拓展阶段,在这个阶段,事务可以申请获得任何数据项上的任何类型的锁,但是不能释放锁;
第二个阶段是释放封锁,也称为收缩阶段,在这个阶段,事务可以释放任何数据项上的任何类型的锁,但是不能再申请任何锁
乐观锁与悲观锁
乐观锁(Optimistic Lock)相对于悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
其实乐观锁机制就是应用到了CAS(Compare And Set)思想,在某些情况下可以减少对数据库、关系或者是记录的加锁,提高并发性能。
乐观锁的实现方式:
- 使用数据版本(version)机制实现
- 时间戳(timestamp)
悲观锁(Pessimistic Lock)需要使用到数据库的锁机制。
在实际生产环境中,如果并发量不大并且不允许脏读,我们就可以使用悲观锁解决并发问题;
但如果系统的并发非常大的话,悲观锁会带来非常大的性能问题,所以我们就要选择乐观锁的方法