JDBC事务控制
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
例如转账,一方多了,一方少了同时执行。
mysql数据库默认事务是自动提交的,即一条SQL语句就是一个单独的事务,是自动提交的。Oracle数据库默认是事务不是自动提交的,没一句都需要手动提交事务,否则默认为都是同一个事务。
对于多条sql放在同一个事务中需要使用事务命令
start transaction 开启事务(所有增删改查都在临时表中进行)
Rollback 回滚事务(取消操作)
Commit 提交事务(确认操作)
在事务管理执行sql,使用数据库内临时表保存,在没有提交时数据库内的真实记录是不会变的,其他用户是无法查看的。且SQL语言中只有DML语句才能被事务管理,即delete,insert,update语句才能使用事务。
当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:
JDBC控制事务语句
Connection.setAutoCommit(false); // 相当于start transaction,jdbc关闭事务自动提交即为从新开启事务。
Connection.rollback(); rollback
Connection.commit(); commit
事务回滚点
SavePonit
当事务特别复杂时,有些情况不需要回滚到开始状态,需要手动设置指定回滚点即存档点。
Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
Conn.commit(); //回滚后必须要提交
将回滚点之前的操作进行提交
事务的特性
原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。?
一致性(Consistency)事务前后数据的完整性必须保持一致。
隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
企业开发中一定要保证事务的原子性,但是事务中出现的复杂问题都是由隔离引起的
事务的隔离级别
多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。
如果事务不考虑隔离就可能会引起以下问题
1,脏读
指一个事务读取了另外一个事务未提交的数据
这是非常危险的,假设A向B转帐100元,对应sql语句如下所示
1.update account set money=money+100 while name=‘b’; 2.update account set money=money-100 while name=‘a’;
当第1条sql执行完,第2条还没执行(A未提交时),如果此时B查询自己的帐户,就会发现自己多了100元钱。如果A等B走后再回滚,B就会损失100元。
脏读是事务中必须要处理的问题
2,不可重复读
在一个事务内读取表中的某一行数据,多次读取结果不同。
例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。
和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。3.虚读
是指在一个事务内读取到了别的事务插入的数据,导致前后读取记录条数不一致。
与不可重复读的区别是,不可重复读是由数据的update引起的,虚读是由数据的插入insert引起的。
数据库为了解决三类隔离引发问题,提供了数据库的隔离级别(所有数据库通用)
Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读 MySQL的默认级别Read committed:可避免脏读情况发生(读已提交) Oracle的默认级别
Read uncommitted:最低级别,以上情况均无法保证。(读未提交)数据库隔离问题的危害;脏读>不可重复读>虚读
安全级别越高,效率越低,危害越小。
set transaction isolation level 设置事务隔离级别
select @@tx_isolation 查询当前事务隔离级别
事务的丢失更新问题
本质上时线程安全问题,两个或多个事务更新同一行,但这些事务彼此之间都不知道其它事务进行的修改,因此第二个更改覆盖了第一个修改
解决方法
悲观锁( Pessimistic Locking )
select * from table lock in share mode(读锁、共享锁)
select * from table for update (写锁、排它锁)
乐观锁( Optimistic Locking )
通过时间戳字段来完成。
悲观锁原理:使用数据库内部的锁机制进行table的锁定,在A修改数据时,A就将数据锁定,B无法进行修改,就不会发生两个事务同时修改,悲观锁在设定的时候就假定丢失数据的操作肯定会发生,为了阻止发生锁定表。
mysql默认情况下,当修改数据时,自动为数据加锁即悲观锁中的读锁(在事务中)----可读但是非当前事务中不可修改防止两个事务同时修改数据。
事务和锁是同时存在的。当事务关闭时锁自动释放。
读锁是可以嵌套的,即一张表可以多次加锁,根据锁的先后来决定具体读写权限。注意当出现死锁的时候即所有事务都处于等待状态的时候,最后发生死锁状态的锁会自动解锁。共享锁是非常容易出现死锁的。
写锁一张表只能加一个排他锁,排它锁与其他锁之间都具有互斥效果。当被加上排它锁之后,在其他事物中再次添加锁时,是只能处于等待查询状态,等待前一个锁的释放之后才会继续执行。不存在并发访问数据的问题,所以不会出现更新丢失。由于mysql默认是读锁,所以添加排他锁的语句指令在底层会自动解除默认的读锁。但是对于手动的读锁必须要手动释放才会解锁。
所以用悲观锁解决数据丢失更新的问题可以使用悲观锁中的排他锁来实现,在多个事务中同时使用排他锁,当第一个事务执行完毕释放锁的时候,其他事务才能继续执行。
乐观锁原理:使用的不是数据库锁机制,二是一个特殊标记字段,判断控制字段状态和内容,得知数据是否发生并发访问
乐观锁是假设更新丢问题不会发生的,通过timestamp事件戳字段进行标记。当一次事务提交的时候,会将读取时的时间戳与表中最新一次updatatime进行比较,如果不一致则事务提交失败,如果一致则事务提交成功并更新updatatime字段。
updatetime timestamp语句作为表中的一列。自动更新为当前时间。