事务
1.1事务的概念
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。
1.2管理事务
1.2.1数据库默认的事务
数据库默认支持事务的,但是数据库默认的事务是一条sql语句独占一个事务,这种模式,意义不大.
1.2.2手动控制事务
如果希望自己控制事务也是可以的:
start transaction;
– 开启事务,在这条语句之后的所有的sql将处在同一事务中,要么同时完成要么同时不完成
–事务中的sql在执行时,并没有真正修改数据库中的数据
commit;
– 提交事务,将整个事务对数据库的影响一起发生
rollback;
– 回滚事务,将这个事务对数据库的影响取消掉
1.2.3JDBC中控制事务
当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:
conn.setAutoCommit(false);
--关闭自动连接后,conn将不会帮我们提交事务,在这个连接上执行的所有sql语句将处在同一事务中,需要我们是手动的进行提交或回滚
conn.commit();
--提交事务
conn.rollback();
--回滚事务
也可以设置回滚点回滚部分事务。
Savepoint sp = conn.setSavepoint();
conn.rollback(sp);
--注意,回到回滚点后,回滚点之前的代码虽然没被回滚但是也没提交呢,如果想起作用还要做commit操作.
例如:
package cn.baidu;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
public class TranDemo {
public static void main(String[] args) throws Exception {
Connection conn = null;
PreparedStatement pstat = null;
Savepoint sp = null;
//加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql:///day20";
conn = DriverManager.getConnection(url,
"root", "root");
//开启事务(将事务的自动提交改为手动提交)
conn.setAutoCommit(false);
//a-520
String sql1="update account set money=money-? where name=?";
pstat = conn.prepareStatement(sql1);
pstat.setDouble(1, 520);
pstat.setString(2, "a");
pstat.executeUpdate();
//b+520
String sql2="update account set money=money+? where name=?";
pstat = conn.prepareStatement(sql2);
pstat.setDouble(1, 520);
pstat.setString(2, "b");
pstat.executeUpdate();
//设置混滚点
sp = conn.setSavepoint();
//人为抛出异常
int x = 5/0;
//b-1040
String sql3="update account set money=money-? where name=?";
pstat = conn.prepareStatement(sql3);
pstat.setDouble(1, 1040);
pstat.setString(2, "b");
pstat.executeUpdate();
//a+1040
String sql4="update account set money=money+? where name=?";
pstat = conn.prepareStatement(sql4);
pstat.setDouble(1, 1040);
pstat.setString(2, "a");
pstat.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//回滚事务
if(conn!=null){
if(sp!=null){
//回滚到sp对应回滚点
conn.rollback(sp);
//勿忘我:将为回滚的部分提交
conn.commit();
}else{
//回滚到事务开启的地方
conn.rollback();
}
}
}finally{
//数据库连接关闭
if(conn!=null){
conn.close();
}
}
}
}
1.3事务的四大特性!!!
1.3.1事务的四大特性
事务的四大特性是事务本身具有的特点。简称ACID。
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。(注意回滚点)
一致性(Consistency)
事务前后数据的完整性必须保持一致。
隔离性(Isolation)
事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
1.4隔离性
1.4.1数据库隔离性分析
数据库的其他三大特性数据库可以帮我们保证,而隔离性我们需要再讨论。
如果我们是数据库的设计者,该如何考虑设计数据库保证数据库的隔离性呢?
我们知道数据库的隔离性问题本质上就是多线程并发安全性问题。可以用锁来解决多线成并发安全问题,但是如果用了锁,必然会造成程序的性能大大的下降.对于数据库这种高并发要求的程序来说这是不可接受的.
我们可以具体分析下隔离性产生的细节:
如果两个线程并发修改,必然产生多线程并发安全问题,必须隔离开;如果两个线程并发查询,必然没有问题,不需要隔离
如果一个线程修改,一个线程查询,在不同的应用场景下有可能有问题,有可能没问题。
1.5隔离性可能造成的问题
- 查询当前数据库的隔离级别:select @@tx_isolation; 修改当前回话的数据库隔离级别: set session transaction isolation level read uncommitted;
1.1.1.脏读:
一个事务读取到另一个事务未提交的数据
1.1.2.不可重复读:
一个事务多次读取数据库中的同一条记录,多次查询的结果不同(一个事务读取到另一个事务已经提交的数据)
1.1.3.虚读(幻读)
有可能出现,有可能不出现:一个事务多次查询整表数据,多次查询时,由于有其他事务增删数据, 造成的查询结果不同(一个事务读取到另一个事务已经提交的数据)
1.6数据库的隔离级别
数据库的四大隔离级别:
read uncommitted;
— 不做任何隔离,可能造成脏读 不可重复度 虚读(幻读)问题
read committed;
– 可以防止脏读,但是不能防止不可重复度 虚读(幻读)问题
repeatable read;
– 可以防止脏读 不可重复度,但是不能防止 虚读(幻读)问题
serializable;
– 可以防止所有隔离性的问题,但是数据库就被设计为了串行化的数据库,性能很低
从安全性上考虑:
Serializable > Repeatable Read > Read Committed > Read uncommitted
从性能上考虑:
Read uncommitted > Read committed > Repeatable Read > Serializable
我们作为数据库的使用者,综合考虑安全性和性能,从四大隔离级别中选择一个在可以防止想要防止的问题的隔离级别中性能最高的一个. 其中Serializable性能太低用的不多,Read uncommitted安全性太低用的也不多,我们通常从Repeatable Read和Read committed中选择一个. 如果需要防止不可重复读选择Repeatable Read,如果不需要防止选择Read committed
***mysql数据库默认的隔离级别就是Repeatable Read
Oracle数据库默认的隔离级别是Read committed***
####1.7操作数据库的隔离级别
1.7.1查询数据库的隔离级别
select @@tx_isolation;
1.7.2修改数据库的隔离级别
set [session/global] transaction isolation level xxxxxx;
不写默认就是session,修改的是当前客户端和服务器交互时是使用的隔离级别,并不会影响其他客户端的隔离级别
如果写成global,修改的是数据库默认的隔离级别(即新开客户端时,默认的隔离级别),并不会修改当前客户端和已经开启的客户端的隔离级别
set global transaction isolation level Repeatable Read;
1.8数据库中的锁:
1.8.1共享锁
共享锁和共享锁可以共存,共享锁和排他锁不能共存.在非Serializable隔离级别下做查询不加任何锁,在Serializable隔离级别下做查询加共享锁.
1.8.2排他锁
排他锁和共享锁不能共存,排他锁和排他锁也不能共存,在任何隔离级别下做增删改都加排他锁.
1.8.3可能的死锁
mysql可以自动检测到死锁,错误退出一方执行另一方
1.9更新丢失问题
1.9.1更新丢失问题的产生
两个并发的事务基于同一个查询结果进行修改,后提交的事务忽略了先提交的事务对数据库的影响,造成了先提交的事务对数据库的影响丢失,这个过程就叫做更新丢失.
将数据库设置为Serializable隔离级别,但是我们一般不会将数据库设置为Serializable
那么在非Serializable下又如何解决更新丢失?可以使用乐观锁、悲观锁。
乐观锁和悲观锁并不是数据库中真实存在的锁,而是两种解决方案的名字。
(1)悲观锁:
在查询时,手动的加排他锁,从而在查询时就排除可能的更新丢失。即 在查询语句后面加上 for update
select paystate from orders where id = 6 for update;
(2)乐观锁:
在表中设计版本version字段,在进行修改时,要求根据具体版本进行修改,并将版本字段+1,如果更新失败,说明更新丢失,需要重新进行更新。
两种解决方案各有优缺点,如果查询多修改少,用乐观锁.如果修改多于查询,用悲观锁。