事务隔离级别

/ Spring / 没有评论 / 439浏览

在上一篇中我们介绍了数据库锁的相关知识,并且知道了数据库锁可以解决并发时数据安全的问题。但是在实际的开发中,如果我们要直接使用数据库锁来解决数据安全问题,那是非常麻烦的。数据库为了让我们更好的更方便的使用数据库锁,于是数据库为用户提供了自动锁机制,只要用户指定了会话的事务隔离级别,数据库就会分析事务中的SQL语句,然后自动为事务操作的数据资源上加上适合的锁。

事务隔离级别主要分为4个,在相同的数据环境下,使用相同的输入,执行相同的工作,设置不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力也是不同的。具体情况如下:

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

事务的隔离级别和数据库并发性是对立的,也就是说如果数据库的隔离级别最高,那么它的并发性也就越低,如果数据库的隔离级别越低,那么它的并发性就越高。

下面我们看一下JDBC对事务的支持。并不是所有的数据库都支持事务,即使支持事务的数据库也并非支持所有的事务隔离级别。

Commection默认情况下是自动提交的,也就是说每条执行的SQL都对应一个事务,为了能够将多条SQL当成一个事务执行,在Java中必须通过设置Connection中的setAutoCommit(false);方法阻止Connection自动提交,并可以通过Connection中的setTransactionIsolation()设置事务的隔离级别,Connection中定义了对应的上面介绍的4个数据库的隔离级别常量。并且可以通过调用Connection中的commit()方法提交事务,通过rollback()方法回滚事务。下面我们通过测试用例来演示在Java中怎么设置事务的隔离级别。

@Testpublic void test()  {  DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();  driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");  driverManagerDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test");  driverManagerDataSource.setUsername("root");  driverManagerDataSource.setPassword("root");  Connection connection = null;  try {    connection = driverManagerDataSource.getConnection();    connection.setAutoCommit(false); // 关闭自动提交机制    connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);    Statement statement = connection.createStatement();    int rows = statement.executeUpdate("INSERT INTO tbUser (strLoginId, strPassword, strName, dtCreateTime) VALUES ('test', 'test', 'test', NOW());");    rows = statement.executeUpdate("UPDATE tbUser SET strName = 'test2' WHERE lid = 2");    connection.commit();  } catch (SQLException e) {    e.printStackTrace();    try {      connection.rollback();    } catch (SQLException e1) {      e1.printStackTrace();    }  }finally {    try {      connection.close();    } catch (SQLException e) {      e.printStackTrace();    }  }}

在JDBC2.0中,事务只能有两个操作,提交和回滚。但是,在我们日常开发时可能需要对事务进行更多的控制,而不是简单的提交和回滚。所以JDBC为了我们可以更加方便的控制,于是在JDBC3.0中引入了一个全新的保存点特性Savepoint。Savepoint接口允许用户将事务分隔为多个阶段,用户可以指定回滚到事务特定的保存点,而并不是向JDBC2.0中那样只能回滚到事务开始的起点。如下图所示:

QQ20170921-225207.png

下面的测试用例将演示如何在Java中使用保存点功能,在特定的问题时,回滚到指定的保存点,而非回滚整个事务。

@Testpublic void test() throws SQLException {  DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();  driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");  driverManagerDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test");  driverManagerDataSource.setUsername("root");  driverManagerDataSource.setPassword("root");  Connection connection = null;  connection = driverManagerDataSource.getConnection();  connection.setAutoCommit(false); // 关闭自动提交机制  connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);  Statement statement = connection.createStatement();  int rows = statement.executeUpdate("INSERT INTO tbUser (strLoginId, strPassword, strName, dtCreateTime) VALUES ('test', 'test', 'test', NOW());");  Savepoint savepoint = connection.setSavepoint("savePoint-1");  rows = statement.executeUpdate("UPDATE tbUser SET strName = 'test2' WHERE lid = 2");  connection.rollback(savepoint);  connection.commit();}

按照上面的代码,在事务进行回滚时,只回滚到了保存点的位置,而保存点之前的操作,并不会回滚,然后在整个事务提交后,就相当于,只将INSERT语句执行并提交了,而UPDATE语句则进行了回滚。

备注:并非所有的数据库都支持保存点功能,用户可以通过DataBaseMetaData中的supportsSavepoints()方法来查看是否支持保存点功能。