事务及事务隔离

事务的4个基本要素:ACID
原子性(Atomicity):
整个事务中的所有操作,要么全部完成,要么全部不做。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性(Consistency):
在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
隔离性(Isolation):
如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。
持久性(Durability):
在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚

Java实现:
不回滚例子:

publicclass testForDB {

   publicstaticvoid main(String[]args) throws UnsupportedEncodingException{

      String driver = "com.mysql.jdbc.Driver";

       String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8";

       String user = "root";

       String password = "123456";

       Connection conn = null;

      try {

         Class.forName(driver);

         conn = DriverManager.getConnection(url, user,password);

          Statement statement = conn.createStatement();

          String sql = "insert into tb_student (Id,StuName) value (22,'xxxxx')";

          statement.execute(sql);

          sql = "insert into tb_student (Id,StuName) value (22,'aaaaaa')";

          statement.execute(sql);

      } catch (Exception e) {

      }

      finally{

         if(conn!=null)

            try {

                conn.close();

            } catch (SQLException e) {}

      }

   }

}

说明:Id字段是主键
执行上面代码数据库会新增一条数据,插入第二条数据的时候出错,第一条数据已经提交了

回滚例子:
注意,首先要设置不自动提交,否则回滚不起作用
1、全部回滚:
还没commit()的数据都被回滚,已经commit()的数据不被回滚

publicclass testForDB {

   publicstaticvoid main(String[]args) throws UnsupportedEncodingException{

      String driver = "com.mysql.jdbc.Driver";

       String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8";

       String user = "root";

       String password = "123456";

       Connection conn = null;

      try {

         Class.forName(driver);

         conn = DriverManager.getConnection(url, user,password);

         conn.setAutoCommit(false);

          Statement statement = conn.createStatement();

          String sql = "insert into tb_student (Id,StuName) value (24,'xxxxx')";

          statement.execute(sql);

          sql = "insert into tb_student (Id,StuName) value (24,'aaaaaa')";

          statement.execute(sql);

          conn.commit();

      } catch (Exception e) {

         if(conn!=null)

            try {

                conn.rollback();//实际上该语句可有可无,因为抛出异常时数据还没有commit

            } catch (SQLException e1) {}

      }

      finally{

         if(conn!=null)

            try {

                conn.close();

            } catch (SQLException e) {}

      }

   }

}

数据库中无新增数据,因为id相同,插入失败。

public class testForDB {

   public static void main(String[]args) throws UnsupportedEncodingException{

      String driver = "com.mysql.jdbc.Driver";

       String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8";

       String user = "root";

       String password = "123456";

       Connection conn = null;

      try {

         Class.forName(driver);

         conn = DriverManager.getConnection(url, user,password);

         conn.setAutoCommit(false);

          Statement statement = conn.createStatement();

          String sql = "insert into tb_student (Id,StuName) value (24,'xxxxx')";

          statement.execute(sql);

          conn.commit();

          sql = "insert into tb_student (Id,StuName) value (25,'xxxxx')";

          statement.execute(sql);

          sql = "insert into tb_student (Id,StuName) value (25,'aaaaaa')";

          statement.execute(sql);

          conn.commit();

      } catch (Exception e) {

         if(conn!=null)

            try {

                conn.rollback();//实际上该语句可有可无,因为抛出异常时数据还没有commit

            } catch (SQLException e1) {}

      }

      finally{

         if(conn!=null)

            try {

                conn.close();

            } catch (SQLException e) {}

      }

   }

}

数据库会新增一条数据(Id等于24)
2、回滚至Savepoint
在commit之前设置Savepoint,回滚的时候指定回滚的位置。
注意:回滚后记得要commit,否则数据全部都没提交,这时就相当于全部回滚了

publicclass testForDB {

   publicstaticvoid main(String[]args) throws UnsupportedEncodingException{

      String driver = "com.mysql.jdbc.Driver";

       String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8";

       String user = "root";

       String password = "123456";

       Connection conn = null;

       Savepoint savepoint1 = null;

       Savepoint savepoint2 = null;

       Savepoint savepoint3 = null;

      try {

         Class.forName(driver);

         conn = DriverManager.getConnection(url, user,password);

         conn.setAutoCommit(false);

          Statement statement = conn.createStatement();

          savepoint1 = conn.setSavepoint("point1");

          String sql = "insert into tb_student (Id,StuName) value (29,'xxxxx')";

          statement.execute(sql);

          savepoint2 = conn.setSavepoint("point2");

          sql = "insert into tb_student (Id,StuName) value (30,'xxxxx')";

          statement.execute(sql);

          savepoint3 = conn.setSavepoint("point3");

          sql = "insert into tb_student (Id,StuName) value (31,'xxxxx')";

          statement.execute(sql);

          sql = "insert into tb_student (Id,StuName) value (31,'aaaaaa')";

          statement.execute(sql);

          conn.commit();

      } catch (Exception e) {

         if(conn!=null)

            try {

                /*

                 * 如果回滚至savepoint1,则数据库一条数据也没有新增,

                 * 回滚至savepoint2,则数据库新增一条Id等于29的数据,

                 * 回滚至savepoint3,则数据库新增两条数据(Id分别为2930

                 */

                conn.rollback(savepoint3);

                conn.commit();

            } catch (SQLException e1) {}

      }

      finally{

         if(conn!=null)

            try {

                conn.close();

            } catch (SQLException e) {}

      }

   }

}

设置Savepoint的回滚比较灵活,可以根据不同的条件回滚到不同的地方

事务隔离

并发问题可归纳为以下几类:

A.丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖(AB事务并发执行,A事务执行更新后,提交;B事务在A事务更新后,B事务结束前也做了对该行数据的更新操作,然后回滚,则两次更新操作都丢失了)。
B.
脏读:一个事务读到另一个事务未提交的更新数据(AB事务并发执行,B事务执行更新后,A事务查询B事务没有提交的数据,B事务回滚,则A事务得到的数据不是数据库中的真实数据。也就是脏数据,即和数据库中不一致的数据)。
C.
不可重复读:一个事务读到另一个事务已提交的更新数据(AB事务并发执行,A事务查询数据,然后B事务更新该数据,A再次查询该数据时,发现该数据变化了)。
D. 
覆盖更新:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据(即A事务更新数据,然后B事务更新该数据,A事务查询发现自己更新的数据变了)。

E.虚读(幻读):一个事务读到另一个事务已提交的新插入的数据(AB事务并发执行,A事务查询数据,B事务插入或者删除数据,A事务再次查询发现结果集中有以前没有的数据或者以前有的数据消失了)。
数据库系统提供了四种事务隔离级别供用户选择:
A.Serializable
(串行化):一个事务在执行过程中完全看不到其他事务对数据库所做的更新(事务执行的时候不允许别的事务并发执行。事务串行化执行,事务只能一个接着一个地执行,而不能并发执行。)。
B.Repeatable Read
(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他其他事务对已有记录的更新。
C.Read Commited
(读已提交数据):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新。
D.Read Uncommitted
(读未提交数据):一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新。


丢失更新

脏读

非重复读

覆盖更新

幻像读

未提交读

Y

Y

Y

Y

Y

已提交读

N

N

Y

Y

Y

可重复读

N

N

N

N

Y

串行化

N

N

N

N

N

隔离级别

数据库系统有四个隔离级别(大多数数据库默认级别为read commited)。对数据库使用何种隔离级别要审慎分析,因为

1. 维护一个最高的隔离级别虽然会防止数据的出错,但是却导致了并行度的损失,以及导致死锁出现的可能性增加。

2. 然而,降低隔离级别,却会引起一些难以发现的bug。

SERIALIZABLE(序列化)

添加范围锁(比如表锁,页锁等,关于range lock,我也没有很深入的研究),直到transaction A结束。以此阻止其它transaction B对此范围内的insert,update等操作。

幻读,脏读,不可重复读等问题都不会发生。

REPEATABLE READ(可重复读)

对于读出的记录,添加共享锁直到transaction A结束。其它transaction B对这个记录的试图修改会一直等待直到transaction A结束。

可能发生的问题:当执行一个范围查询时,可能会发生幻读。

READ COMMITTED(提交读)

在transaction A中读取数据时对记录添加共享锁,但读取结束立即释放。其它transaction B对这个记录的试图修改会一直等待直到A中的读取过程结束,而不需要整个transaction A的结束。所以,在transaction A的不同阶段对同一记录的读取结果可能是不同的。

可能发生的问题:不可重复读。

READ UNCOMMITTED(未提交读)

不添加共享锁。所以其它transaction B可以在transaction A对记录的读取过程中修改同一记录,可能会导致A读取的数据是一个被破坏的或者说不完整不正确的数据。

另外,在transaction A中可以读取到transaction B(未提交)中修改的数据。比如transaction B对R记录修改了,但未提交。此时,在transaction A中读取R记录,读出的是被B修改过的数据。

可能发生的问题:脏读。


















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值