目录
概念
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功 ;
例如: A——B转账,对应如下两条sql语句 ;
update from account set money = money + 100 where name = 'b' ; // 数据库中不支持 +=
update from account set money = money - 100 where name = 'a' ;
数据库默认事务是自动提交的,也就是发一条sql它就执行一条 ;
这样显然不能满足我们的需求,我们需要拿 2
条 sql
一起执行,要么全部成功,要么全部失败 ;因此,我们需要使用事务 ;
命令行使用事务
如果想多条sql放在一个事务中执行,则需要如下操作 :
------------------------------------------
start transcation 开启事务
。。。。
需要将要一起执行的一组语句,放在中间 ;
。。。。
Commit 提交事务
------------------------------------------
命令行操控事务命令
start transcation 开启事务 Rollback 回滚事务 ,对一次开启事务的操作,进行回滚,也就是手动回滚; Commit 提交事务 对事务的操作,需要放在他们之间,只要数据库,没有收到 commit,他就认为事务执行失败,会进行操作的回滚,也就是回滚事务 ; 。
使用事务(JDBC)
当 JDBC
程序向数据库获得一个 Connection
对象时,默认情况下,这个Connection
连接是没有开启事务的 ,它会自动提交它上面的 sql
语句;
若想关闭这种默认提交方式,让多条 SQL
在一个事务中执行,可以使用下列语句:
// 相当于开启了事务: start transaction ;
Connection.setAutoCommit(false) ;
// rollback ;
Connection.rollback() ;
// commit ;
Connection.commit() ;
回滚事务点
Statement.setSavePoint() ;
返回代表回滚点的对象 SavePoint
;
我们可以控制回滚到哪一个事务点 rollback(sp)
,但是我们自己控制回滚一定要记得提交事务,否则数据库发现没有提交,自己就又会进行回滚事务 。
代码实例
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
Savepoint savepoint = null ;
try {
connection = JdbcUtils.getConnection();
// 开启事务
connection.setAutoCommit(false);
String sql1 = "update employee set money = money - 100 where name = 'a' ";
statement = connection.prepareStatement(sql1);
// 执行sql语句
statement.executeUpdate();
// 設置回滚点
savepoint = connection.setSavepoint() ;
String sql2 = "update employee set money = money + 100 where name = 'b' ";
statement = connection.prepareStatement(sql2);
// 执行sql语句
statement.executeUpdate();
// 发生异常
int num = 1/0 ;
// 提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
// 发生异常,回滚到我们设置的事务回滚点
connection.rollback(savepoint);
// 这里必须再次进行 提交事务,否则数据库即使回滚到事务点了,但是没有提交事务,数据库就自己再次回滚了。。
connection.commit();
} finally {
JdbcUtils.closedConnection(resultSet, statement, connection);
}
事务的四大特性(ACID)
·原子性 : 指事务是一个不可分割的工作单位,事务中的操作都发生,要么都不发生 ;
·一致性 : 指事务前后数据的完整性必须保持一致 ;
例如:存钱的例子,必须保证事务前后,表中的总钱数没有变化!
·隔离性 : 事务的隔离性是指多个用户并发访问数据库时,一个用户的事务,不能被其他用户的事务干扰,多个并发事务之间的数据要相互隔离 ;
·持久性 : 指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下里即使数据库发生故障也不应该对其有任何影响 ;
事务的隔离级别
·多个线程开启各自事务操作数据库的数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据的时候的准确性 ;
·如果不考虑隔离性,可能会引发如下问题:
·脏读 :
指一个事务读取了另外一个事务 ‘未提交’ 的数据 ;
这是非常危险的,假设A向B转账100元,对应的sql语句是:
update from account set money = money + 100 where name = 'b' ;// 数据库中不支持 +=
update from account set money = money - 100 where name = 'a' ;
当第一条SQL语句执行完,第二条还没执行(A未提交时),如果此时B查询自己的账户;
就会发现自己多了100元钱。如果A等B走后,再回滚,B就会损失100元 ;
---帮助理解的例子:A在网上买了B的东西,需要付款100元;然后A开启一个事务,向B转100元,然后并不提交事务,就打电话给B ,问他钱收到没。B看下自己的账户收到钱了,就说收到了,然后发货;这时候A确定B发货了,就进行事务的回滚,这样转给B的100元,就直接回来了。。。A或成最大赢家 ;
·不可重复读:
在一个事务内,读取表的某一行数据,多次读取,结果不同 ;(指一个事务读取了另外一个事务 ‘已提交’ 的数据 ;针对读表中的同一行数据)
例如:在银行查询A账户余额,第一次查询A账户为200元,此时A向账户存了100元并提交了,此时A账户为300元,。银行2次查询不一致,可能会疑惑,不知道哪一次查询是准确的 ;
·和脏读的区别就是,脏读是读取前一个事务未提交的脏数据;不可重复读是重新读取了前一事务已提交的数据 ;
·很多人认为这种情况是对滴,没有什么疑惑,理所当然的以后面的为准;其实,我们应该考虑一个情况,比如银行程序需要查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行了2次查询,导致文件个屏幕中的结果不一致,银行工作人员就不知道以哪个为准了 ;
---帮助理解的例子 : 银行年度报表,开启一个事务,读表,生成一份报表,给国务院送去,比如现在是1000万亿;然后这个事务没有提交,继续生成另一份报表给总理送去,但是这是,有人给银行转账100亿,并提交事务了;所有第二份报表多了100亿。这样两份报表的数据对不上了 ;
·虚读(幻读,针对的是一张表)
·是指在一个事务内读取到别的事务‘插入’的数据,导致前后读取不一致;
·如甲存款100元未提交,这时候银行报表做报表统计account 表中,所有用户的总额为500元;然后甲提交了;这时候在统计账户为600元,也会发现两个甲,造成虚读;
事务隔离级别的设置语句(从高到低)
·Serializable : 可避免脏读、不可重复读、幻读的情况产生 ;(串行化) 跟加锁一样,只要有链接在操作这张表,其他连接就必须等着,所有性能最差 ;
·Repeatable read : 可避免脏读、不可重复读(但是可以幻读) --- 这个看运气,有时候可以幻读,有时候不可以 ;(mysql默认级别)
·Read commit : 可避免脏读 ;
·Read uncommitted : 最低级别,以上情况都不能保证 ;
set transaction isolation level ; // 设置事务隔离级别,随连接有效,关闭连接;
select @@tx_isolation ; // 查询当前事务的隔离级别 ;
出现四种级别是因为,越往上级别,越消耗数据库的性能,因此给出四个级别,供程序员自己选择适合的级别 ;
mysql数据库默认级别是: ·Repeatable read ;mysql是严格按照数据库规范设计的,有四个级别;
oracle数据库默认级别是:·Read commit : 可避免脏读 ,oracle只有2个级别(1 和 3 );
JDBC改变每次连接的隔离级别: connection.setTransactionLevel(int level) ;
一般默认级别就OK了 ;