事务指逻辑上的一组操做,组成这组操做的各个单元,要不所有成功,要不所有不成功。
例如:A——B转账,对应于以下两条sql语句
update from account set money=money+100 where name='B';
update from account set money=money-100 where name='A';java
一、编写测试SQL脚本,以下:mysql
1 /*建立帐户表*/ 2 create table account( 3 id int primary key auto_increment, 4 name varchar(40), 5 money float 6 ); 7 8 /*插入测试数据*/ 9 insert into account(name,money) values('A',1000); 10 insert into account(name,money) values('B',1000); 11 insert into account(name,money) values('C',1000);
下面咱们在MySQL数据库中模拟A——B转账这个业务场景sql
使用"start transaction"开启MySQL数据库的事务,以下所示:数据库
咱们首先在数据库中模拟转帐失败的场景,首先执行update语句让A用户的money减小100块钱,以下图所示:并发
而后咱们关闭当前操做的dos命令行窗口,这样就致使了刚才执行的update语句的数据库的事务没有被提交,那么咱们对A用户的修改就不算是是真正的修改了,下次在查询A用户的money时,依然仍是以前的1000,以下图所示:测试
下面咱们在数据库模拟A——B转帐成功的场景spa
咱们手动提交(commit)数据库事务以后,A——B转帐100块钱的这个业务操做算是真正成功了,A帐户中少了100,B帐户中多了100。命令行
经过手动回滚事务,让全部的操做都失效,这样数据就会回到最初的初始状态!线程
当Jdbc程序向数据库得到一个Connection对象时,默认状况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可以使用下列的JDBC控制事务语句code
在JDBC代码中演示银行转账案例,使以下转账操做在同一事务中执行
"update account set money=money-100 where name='A'"
update account set money=money+100 where name='B'
代码以下:
1 package me.gacl.demo; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import me.gacl.utils.JdbcUtils; 8 import org.junit.Test; 9 10 /** 11 * @ClassName: TransactionDemo1 12 * @Description: 13 * JDBC中使用事务来模似转账 14 create table account( 15 id int primary key auto_increment, 16 name varchar(40), 17 money float 18 ); 19 insert into account(name,money) values('A',1000); 20 insert into account(name,money) values('B',1000); 21 insert into account(name,money) values('C',1000); 22 * @author: 孤傲苍狼 23 * @date: 2014-9-22 下午11:16:17 24 * 25 */ 26 public class TransactionDemo1 { 27 28 /** 29 * @Method: testTransaction1 30 * @Description: 模拟转帐成功时的业务场景 31 * @Anthor:孤傲苍狼 32 * 33 */ 34 @Test 35 public void testTransaction1(){ 36 Connection conn = null; 37 PreparedStatement st = null; 38 ResultSet rs = null; 39 40 try{ 41 conn = JdbcUtils.getConnection(); 42 conn.setAutoCommit(false);//通知数据库开启事务(start transaction) 43 String sql1 = "update account set money=money-100 where name='A'"; 44 st = conn.prepareStatement(sql1); 45 st.executeUpdate(); 46 String sql2 = "update account set money=money+100 where name='B'"; 47 st = conn.prepareStatement(sql2); 48 st.executeUpdate(); 49 conn.commit();//上面的两条SQL执行Update语句成功以后就通知数据库提交事务(commit) 50 System.out.println("成功!!!"); //log4j 51 }catch (Exception e) { 52 e.printStackTrace(); 53 }finally{ 54 JdbcUtils.release(conn, st, rs); 55 } 56 } 57 58 /** 59 * @Method: testTransaction1 60 * @Description: 模拟转帐过程当中出现异常致使有一部分SQL执行失败后让数据库自动回滚事务 61 * @Anthor:孤傲苍狼 62 * 63 */ 64 @Test 65 public void testTransaction2(){ 66 Connection conn = null; 67 PreparedStatement st = null; 68 ResultSet rs = null; 69 70 try{ 71 conn = JdbcUtils.getConnection(); 72 conn.setAutoCommit(false);//通知数据库开启事务(start transaction) 73 String sql1 = "update account set money=money-100 where name='A'"; 74 st = conn.prepareStatement(sql1); 75 st.executeUpdate(); 76 //用这句代码模拟执行完SQL1以后程序出现了异常而致使后面的SQL没法正常执行,事务也没法正常提交,此时数据库会自动执行回滚操做 77 int x = 1/0; 78 String sql2 = "update account set money=money+100 where name='B'"; 79 st = conn.prepareStatement(sql2); 80 st.executeUpdate(); 81 conn.commit();//上面的两条SQL执行Update语句成功以后就通知数据库提交事务(commit) 82 System.out.println("成功!!!"); 83 }catch (Exception e) { 84 e.printStackTrace(); 85 }finally{ 86 JdbcUtils.release(conn, st, rs); 87 } 88 } 89 90 /** 91 * @Method: testTransaction1 92 * @Description: 模拟转帐过程当中出现异常致使有一部分SQL执行失败时手动通知数据库回滚事务 93 * @Anthor:孤傲苍狼 94 * 95 */ 96 @Test 97 public void testTransaction3(){ 98 Connection conn = null; 99 PreparedStatement st = null; 100 ResultSet rs = null; 101 102 try{ 103 conn = JdbcUtils.getConnection(); 104 conn.setAutoCommit(false);//通知数据库开启事务(start transaction) 105 String sql1 = "update account set money=money-100 where name='A'"; 106 st = conn.prepareStatement(sql1); 107 st.executeUpdate(); 108 //用这句代码模拟执行完SQL1以后程序出现了异常而致使后面的SQL没法正常执行,事务也没法正常提交 109 int x = 1/0; 110 String sql2 = "update account set money=money+100 where name='B'"; 111 st = conn.prepareStatement(sql2); 112 st.executeUpdate(); 113 conn.commit();//上面的两条SQL执行Update语句成功以后就通知数据库提交事务(commit) 114 System.out.println("成功!!!"); 115 }catch (Exception e) { 116 try { 117 //捕获到异常以后手动通知数据库执行回滚事务的操做 118 conn.rollback(); 119 } catch (SQLException e1) { 120 e1.printStackTrace(); 121 } 122 e.printStackTrace(); 123 }finally{ 124 JdbcUtils.release(conn, st, rs); 125 } 126 } 127 }
在开发中,有时候可能须要手动设置事务的回滚点,在JDBC中使用以下的语句设置事务回滚点
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
Conn.commit();//回滚后必须通知数据库提交事务
设置事务回滚点范例:
1 package me.gacl.demo; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sql.ResultSet; 6 import java.sql.SQLException; 7 import java.sql.Savepoint; 8 9 import me.gacl.utils.JdbcUtils; 10 import org.junit.Test; 11 12 /** 13 * @ClassName: TransactionDemo1 14 * @Description: 15 * JDBC中使用事务来模似转账 16 create table account( 17 id int primary key auto_increment, 18 name varchar(40), 19 money float 20 ); 21 insert into account(name,money) values('A',1000); 22 insert into account(name,money) values('B',1000); 23 insert into account(name,money) values('C',1000); 24 * @author: 孤傲苍狼 25 * @date: 2014-9-22 下午11:16:17 26 * 27 */ 28 public class TransactionDemo2 { 29 30 /** 31 * @Method: testTransaction1 32 * @Description: 模拟转帐成功时的业务场景 33 * @Anthor:孤傲苍狼 34 * 35 */ 36 @Test 37 public void testTransaction1(){ 38 Connection conn = null; 39 PreparedStatement st = null; 40 ResultSet rs = null; 41 Savepoint sp = null; 42 43 try{ 44 conn = JdbcUtils.getConnection(); 45 conn.setAutoCommit(false);//通知数据库开启事务(start transaction) 46 47 String sql1 = "update account set money=money-100 where name='A'"; 48 st = conn.prepareStatement(sql1); 49 st.executeUpdate(); 50 51 //设置事务回滚点 52 sp = conn.setSavepoint(); 53 54 String sql2 = "update account set money=money+100 where name='B'"; 55 st = conn.prepareStatement(sql2); 56 st.executeUpdate(); 57 58 //程序执行到这里出现异常,后面的sql3语句执行将会中断 59 int x = 1/0; 60 61 String sql3 = "update account set money=money+100 where name='C'"; 62 st = conn.prepareStatement(sql3); 63 st.executeUpdate(); 64 65 conn.commit(); 66 67 }catch (Exception e) { 68 try { 69 /** 70 * 咱们在上面向数据库发送了3条update语句, 71 * sql3语句因为程序出现异常致使没法正常执行,数据库事务而已没法正常提交, 72 * 因为设置的事务回滚点是在sql1语句正常执行完成以后,sql2语句正常执行以前, 73 * 那么通知数据库回滚事务时,不会回滚sql1执行的update操做 74 * 只会回滚到sql2执行的update操做,也就是说,上面的三条update语句中,sql1这条语句的修改操做起做用了 75 * sql2的修改操做因为事务回滚没有起做用,sql3因为程序异常没有机会执行 76 */ 77 conn.rollback(sp);//回滚到设置的事务回滚点 78 //回滚了要记得通知数据库提交事务 79 conn.commit(); 80 } catch (SQLException e1) { 81 e1.printStackTrace(); 82 } 83 e.printStackTrace(); 84 }finally{ 85 JdbcUtils.release(conn, st, rs); 86 } 87 } 88 }
原子性是指事务是一个不可分割的工做单位,事务中的操做要么所有成功,要么所有失败。好比在同一个事务中的SQL语句,要么所有执行成功,要么所有执行失败
官网上事务一致性的概念是:事务必须使数据库从一个一致性状态变换到另一个一致性状态。以转帐为例子,A向B转帐,假设转帐以前这两个用户的钱加起来总共是2000,那么A向B转帐以后,无论这两个帐户怎么转,A用户的钱和B用户的钱加起来的总额仍是2000,这个就是事务的一致性。
事务的隔离性是多个用户并发访问数据库时,数据库为每个用户开启的事务,不能被其余事务的操做数据所干扰,多个并发事务之间要相互隔离。
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即便数据库发生故障也不该该对其有任何影响
事务的四大特性中最麻烦的是隔离性,下面重点介绍一下事务的隔离级别
多个线程开启各自事务操做数据库中数据时,数据库系统要负责隔离操做,以保证各个线程在获取数据时的准确性。
若是事务不考虑隔离性,可能会引起以下问题:
脏读指一个事务读取了另一个事务未提交的数据。
这是很是危险的,假设A向B转账100元,对应sql语句以下所示
1.update account set money=money+100 where name='B';
2.update account set money=money-100 where name='A';
当第1条sql执行完,第2条还没执行(A未提交时),若是此时B查询本身的账户,就会发现本身多了100元钱。若是A等B走后再回滚,B就会损失100元。
不可重复读指在一个事务内读取表中的某一行数据,屡次读取结果不一样。
例如银行想查询A账户余额,第一次查询A账户为200元,此时A向账户内存了100元并提交了,银行接着又进行了一次查询,此时A账户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
不可重复读和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是从新读取了前一事务已提交的数据。
不少人认为这种状况就对了,无须困惑,固然是后面的为准。咱们能够考虑这样一种状况,好比银行程序须要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,致使文件和屏幕中的结果不一致,银行工做人员就不知道以哪一个为准了。
虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,致使先后读取不一致。
如丙存款100元未提交,这时银行作报表统计account表中全部用户的总额为500元,而后丙提交了,这时银行再统计发现账户为600元了,形成虚读一样会使银行不知所措,到底以哪一个为准。
MySQL数据库共定义了四种隔离级别:
mysql数据库查询当前事务隔离级别:select @@tx_isolation
例如:
mysql数据库默认的事务隔离级别是:Repeatable read(可重复读)
mysql数据库设置事务隔离级别:set transaction isolation level 隔离级别名
例如:
同时打开两个窗口模拟2个用户并发访问数据库
A窗口
set transaction isolation level read uncommitted;--设置A用户的数据库隔离级别为Read uncommitted(读未提交)
start transaction;--开启事务
select * from account;--查询A帐户中现有的钱,转到B窗口进行操做
select * from account--发现a多了100元,这时候A读到了B未提交的数据(脏读)
B窗口
start transaction;--开启事务
update account set money=money+100 where name='A';--不要提交,转到A窗口查询
A窗口
set transaction isolation level read committed;
start transaction;
select * from account;--发现a账户是1000元,转到b窗口
select * from account;--发现a账户多了100,这时候,a读到了别的事务提交的数据,两次读取a账户读到的是不一样的结果(不可重复读)
B窗口
start transaction;
update account set money=money+100 where name='aaa';
commit;--转到a窗口
A窗口
set transaction isolation level repeatable read;
start transaction;
select * from account;--发现表有4个记录,转到b窗口
select * from account;--可能发现表有5条记录,这时候发生了a读取到另一个事务插入的数据(虚读)
B窗口
start transaction;
insert into account(name,money) values('ggg',1000);
commit;--转到a窗口
A窗口 set transaction isolation level Serializable; start transaction; select * from account;--转到b窗口 B窗口 start transaction; insert into account(name,money) values('ggg',1000);--发现不能插入,只能等待a结束事务才能插入