企业级应用架构模型-并发,事务,锁
一、并发问题的产生
多线程/进程同时操作(读/写)同一数据
二、并发问题的种类
- 丢失更新(lost update)
- 第一类更新丢失(回滚丢失): 当2个事务更新相同的数据源,如果第一个事务被提交,而另外一个事务却被撤销,那么会连同第一个事务所做的跟新也被撤销。也就是说第一个事务做的跟新丢失了。
- 第二类更新丢失(覆盖丢失): 第二类更新丢失实在实际应用中经常遇到的并发问题,他和不可重复读本质上是同一类并发问题,通常他被看做不可重复读的特例。当2个或这个多个事务查询同样的记录然后各自基于最初的查询结果更新该行时,会造成第二类丢失更新。因为每个事务都不知道不知道其他事务的存在,最后一个事务对记录做的修改将覆盖其他事务对该记录做的已提交的跟新。
- 脏读(dirty read)
- 脏读(事务没提交,提前读取):脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
- 不可重复读(non-repeatable read)
- 不可重复读(两次读的不一致) :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
- 幻读(phantom read)
- 幻读(发生在其他事务插入或删除后):是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
http://msdn.microsoft.com/en-us/library/aa213029(v=sql.80).aspx
http://www.cnblogs.com/pyq228/archive/2012/05/26/2519447.html
三、解决方案
- 隔离——使数据局部化(即,使用非共享数据、线程独享),则线程安全
- 识别不变的数据——尽量使用常量
- 加锁——对不得不共享的可变数据进行加锁
四、乐观锁策略——冲突检测
乐观锁策略通常是建立在数据的某种版本标记上。为了检测“丢失更新”,系统核对将要更新的数据的版本标记和共享数据的版本标记,如果两者一样,系统更新数据并更新版本标记,否则,只能放弃本次更新,从头再来。
五、悲观锁策略——冲突避免
读锁(共享锁S):对读锁开放,对写锁封闭( lock table 表名 in share mode)
写锁(排他说X):对读锁和写锁都封闭( select * from 表名 where 条件 for update)
六、死锁——悲观锁会导致死锁
死锁解除方案:死锁超时控制(会导致误伤持锁时间长的)、死锁检测机制
死锁预防方案:
- 强制在开始时就获得所有可能需要的锁,此后就不允许再获得更多的锁。
- 规定每个人获取锁的顺序。例如按字母顺序。
- 当取不到锁的时候,自动牺牲。
- 。。。。
七、事务——处理并发问题的主要工具
ACID属性:原子性(Actomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
使用事务时,要防止 锁升级(lock escalation),如果一个事务锁住了一个表中的许多行,则数据库可能无法处理那么多锁,只能将锁升级到锁住整个表。
八、事务隔离级别——事务之间用锁相互隔开
隔离级别就是对事务并发控制的等级。ANSI/ ISO SQL将其分为串行化(SERIALIZABLE)、可重复读(REPEATABLE READ)、读已提交(READ COMMITED)、读未提交(READ UNCOMMITED)四个等级。为了实现隔离级别通常数据库 采用锁(Lock)。一般在编程的时候只需要设置隔离等级,至于具体采用什么锁则由数据库来设置。
九、JDBC定义的事务隔离级别
- Connection. TRANSACTION_NONE 说明不支持事务。
- Connection. TRANSACTION_READ_UNCOMMITTED 说明在提交前一个事务可以看到另一个事务的变化。这样脏读、不可重复的读和虚读都是允许的。
- Connection. TRANSACTION_READ_COMMITTED 说明读取未提交的数据是不允许的。这个级别仍然允许不可重复的读和虚读产生。
- Connection. TRANSACTION_REPEATABLE_READ 说明事务保证能够再次读取相同的数据而不会失败,但虚读仍然会出现。
- Connection. TRANSACTION_SERIALIZABLE 是最高的事务级别,它防止脏读、不可重复的读和虚读。
十、Spring事务隔离级别的定义和配置
- TransactionDefinition.ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.另外四个与JDBC的隔离级别相对应
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读.
- TransactionDefinition.ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。
- TransactionDefinition.ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
- TransactionDefinition.ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" />
</tx:attributes>
</tx:advice>
十一、几大数据库的默认事务隔离级别
隔离级别每种数据库都不一样,如果不指定这个属性的话,就是 default
- SQL Server :Read Commited
- Oracle: Read Commited
- MySQL : Repeatable Read
附录一:Oracle的锁
Oracle锁分为两大类:数据锁(DML锁)和字典锁。字典锁包括语法分析锁和DDL锁,有DBMS控制,用户无权控制。
Oracle 5种数据锁:共享锁、排他锁、行级共享锁(RS锁)、行级排他锁(RX锁)、共享行级排他锁(SRX锁)。加锁粒度包括行级和表级。
数据锁相容矩阵:
S X RS RX SRX
S Y N Y N N
X N N N N N
RS Y N Y Y Y
RX N N Y Y N
SRX N N Y N N
一般情况下,Oracle自行加锁,用户也可以通过lock table等语句进行加锁。
Oracle默认情况下,读数据不加锁,而是通过回滚段防止脏读和保证可重复读。
Oracle具有死锁检查功能,周期性检查系统是否有死锁,如果存在死锁,则撤销执行更新操作次数最少的事务。
附录二:JDBC事务隔离级别的测试
- @Test
- public void testDbTransactionIsolation() throws ClassNotFoundException,
- SQLException {
- Connection cn = getConnection();
- System.out.print("默认的事务隔离级别是:");
- printTransactionIsolation(cn);
- setTransactionIsolation(cn, Connection.TRANSACTION_NONE,
- "TRANSACTION_NONE");
- setTransactionIsolation(cn, Connection.TRANSACTION_READ_UNCOMMITTED,
- "TRANSACTION_READ_UNCOMMITTED");
- setTransactionIsolation(cn, Connection.TRANSACTION_READ_COMMITTED,
- "TRANSACTION_READ_COMMITTED");
- setTransactionIsolation(cn, Connection.TRANSACTION_REPEATABLE_READ,
- "TRANSACTION_REPEATABLE_READ");
- setTransactionIsolation(cn, Connection.TRANSACTION_SERIALIZABLE,
- "TRANSACTION_SERIALIZABLE");
- cn.close();
- }
- private void setTransactionIsolation(Connection cn, int level,
- String levelDiscription) {
- try {
- System.out.print("设置事务隔离级别为:" + levelDiscription + ",");
- cn.setTransactionIsolation(level);
- System.out.print("设置成功,");
- printTransactionIsolation(cn);
- } catch (Exception e) {
- System.out.println("设置失败:" + e.getMessage());
- }
- }
- private void printTransactionIsolation(Connection cn) throws SQLException {
- int level = cn.getTransactionIsolation();
- if (level == Connection.TRANSACTION_NONE)
- System.out.println("TRANSACTION_NONE");
- else if (level == Connection.TRANSACTION_READ_UNCOMMITTED)
- System.out.println("TRANSACTION_READ_UNCOMMITTED");
- else if (level == Connection.TRANSACTION_READ_COMMITTED)
- System.out.println("TRANSACTION_READ_COMMITTED");
- else if (level == Connection.TRANSACTION_REPEATABLE_READ)
- System.out.println("TRANSACTION_REPEATABLE_READ");
- else if (level == Connection.TRANSACTION_SERIALIZABLE)
- System.out.println("TRANSACTION_SERIALIZABLE");
- }