Spring 的事务管理学习笔记

来源:互联网 发布:折800是淘宝官方活动吗 编辑:程序博客网 时间:2024/05/22 06:21

数据库事务基础知识


什么是数据库事务


数据库事务有严格的定义,它必须同时满足4个特性:原子性,一致性,隔离性和持久性。

  • 原子性:表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作执行成功,整个事务才提交,事务中任何一个数据库操作失败了,已经执行的任何操作都必须撤销,让数据库返回到初始状态。
  • 一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。
  • 隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。
  • 持久性:一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须保证能够通过某种机制恢复数据。

在这些事务特性中,数据“一致性”是最终目标,其它的特性都是为达到这个目标的措施、要求或手段。


数据并发的问题


数据库中的相同数据可能同时被多个事务访问,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。这些问题可以归结为5类:包括3类数据读问题(脏读,不可重复读和幻想读)以及2类数据更新问题(第一类丢失更新和第二类丢失更新)。下面来了解这些问题。


脏读


A事务选择的B事务尚未提交的更改数据并在这个数据的基础上操作如果恰巧B事务回滚,那么A事务读到的数据根本是不被承认的。

例如,一个编辑人员正在更改电子文档。在更改过程中,另一个编辑人员复制了该文档(该复本包含到目前为止所做的全部更改)并将其分发给预期的用户。此后,第一个编辑人员认为目前所做的更改是错误的,于是删除了所做的编辑并保存了文档。分发给用户的文档包含不再存在的编辑内容,并且这些编辑内容应认为从未存在过。假如在第一个编辑人员确定最终更改前任何人都不能读取更改的文档,则可以避免该问题。   


不可重复读


不可重复读是指A事务读取了B事务已经提交的更改数据。

例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。假如只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。



幻想读


A事务读取B事务提交的新增数据,这时A事务将出现幻想读的问题。

例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。假如在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。

注:幻想读和不可重复读是两个容易混淆的概念,前者是指读到了其它已经提交事务的新增数据,而后者是指读到了已经提交事务的更改数据(更改或删除),为了避免这两种情况,采取的对策是不同的,防止读取到更改数据,只需要对操作的数据添加行级锁,阻止操作中的数据发生变化,而防止读取到新增数据,则往往需要添加表级锁——将整个表锁定,防止新增数据。


第一类丢失更新


A事务撤销时,把已经提交的B事务的更新数据覆盖了。

例如,两个编辑人员A,B制作了同一文档的电子复本。每个编辑人员独立地更改其复本,最后的复本由最后一次更改确定。A保存复本之后,B也保存复本此时,A发现错误,将更正的复本提交,之后又发现之前提交的复本错误,将复本改为第一次自己提交的复本,这样就覆盖了原始文档。假如在第一个编辑人员完成之后第二个编辑人员才能进行更改,则可以避免该问题。


第二类丢失更新


A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失。

例如,两个编辑人员制作了同一文档的电子复本。每个编辑人员独立地更改其复本最后的复本由最后一次更改确定B保存复本之后,A又保存复本,这样就 覆盖了原始文档。


数据库锁机制


数据库通过锁的机制解决并发访问的问题。

按锁定的对象不同,一可以分为表锁定和行锁定,前者对整个表进行锁定,而后者对表中特定行进行锁定。从并发事务锁定的关系上看,可以分为共享锁定和独占锁定。共享锁定会防止独占锁定,但允许其它的共享锁定。而独占锁定既防止其它的独占锁定,也防止其它的共享锁定。下面我们来看Oracle数据库常用的5种锁定。

  • 行共享锁定:一般通过SELECT FOR UPDATE语句隐式获得行共享锁定,在Oracle中也可以通过LOCK TABLE IN ROW SHARE MODE语句显式获得行共享锁定。行共享锁定并不防止对数据行进行更改的操作,但是可以防止其它会话获取独占性数据表锁定。允许进行多个并发的行共享和行独占性锁定,还允许进行数据表的共享或者采用共享行独占锁定。
  • 行独占锁定:通过INSERT、UPDATE或DELETE语句隐式获取,或者通过LOCK TABLE IN ROW EXCLUSIVE MODE语句显式获取。这个锁定可以防止其它会话获取一个共享锁定、共享行独占锁定或独占锁定。
  • 表共享锁定:通过LOCK TABLE IN SHARE MODE语句显式获得。这种锁定可以防止其它会话获取行独占锁定,或者防止其它表共享行独占或表独占锁定,它允许在表中拥有多个行共享和表共享锁定。
  • 表共享行独占:通过LOCK TABLE IN SHARE ROW EXCLUSIVE MODE语句显式获得。这种锁定可以防止其它会话获取一个表共享、行独占或者表独占锁定,它允许其它行共享锁定。
  • 表独占:通过LOCK TABLE IN EXCLUSIVE MODE显式获得。这个锁定防止其它会话对该表的任何其它锁定。


事务隔离级别


尽管数据库为用户提供了锁的DML操作方式,但直接使用锁管理是非常麻烦的,因此数据库为用户提供了自动锁机制。只要用户指定会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源添加上适合的锁。

ANSI/ISO SQL 92标准定义了4个等级的事务隔离级别,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同的事务隔离级别能够解决的数据并发问题的能力是不同的,如下所示:


隔离级别脏读不可重复读幻想读第一类丢失更新第二类丢失更新READ UNCOMMITED允许允许允许不允许允许READ COMMITTED不允许允许允许不允许允许REPEATABLE READ不允许不允许允许不允许不允许SERIALIZABLE不允许不允许不允许不允许不允许



JDBC对事务支持


并不是所有的数据库都支持事务,即使支持事务的数据库也 并非支持所有的事务隔离级别,用户可以通过Connection#getMetaData()方法获取DatabaseMetaData对象,并通过该对象的supportsTransactions()、supportsTransactionIsoIationLevel(int level)方法查看底层数据库的事务支持情况。

Connection默认情况下是自动提交的,即每条执行的SQL都对应一个事务,为了能够将多条SQL当成一个事务执行,必须先通过Connection#setAutoCommit(false)阻止Connection自动提交,并可通过Connection#setTransactionIsoIation()设置事务的隔离级别。典型的JDBC事务数据操作如下:
Connection conn;try{conn = DriverManager.getConnection();conn.setAutoCommit(false);conn.setTransactionlsolation(Connection.TRANSACTION_SERIAIZABLE);Statement stmt = conn.createStatement();int rows = stmt.executeUpdate("INSERT INTO t_topic VALUES(1,"TOM")");rows = stmt.executeUpdate("UPDATE t_user set topic_nums=topic_nums+1 WHERE user_id = 1");conn.commit();}catch(Exception e){...conn.rollback();}finally{...}

JDBC3.0提供保存点特性,Savepoint接口允许用户将事务分割为多个阶段,用户可以指定回滚到事务的特定保存点。下面代码使用了保存点的功能,在发生特定问题时,可以回滚到指定的保存点,而非回滚整个事务。

...Statement stmt = conn.createStatement();int rows = stmt.executeUpdate("INSERT INTO t_topicVALUES(1,"tom")");Savepoint svpt = conn.setSavepoint("savePoint1");rows = stmt.executeUpdate("UPDATE t_user set topic_nums = topic_nums+1 WHERE user_id = 1");...conn.rollback(svpt);...conn.commit();


并非所有数据库都支持保存点功能,可通过DatabaseMetaData#supportsSavepoints()方法查看是否支持。







未完待续...