环境
系统:win10家庭中文版
数据库:mysql Ver 14.14 Distrib 5.5.15, for Win32 (x86)
事务
一组逻辑操作单元,使数据从一种状态变换到另一种状态.
这里将逻辑操做单元理解为一条SQL语句。
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
#事务的特性(ACID)
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。(中间失败就回滚)
隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。(见下图解析)
持久性(Durability)
持久性是指一个事务一旦被提交(commit),它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
首先,原子性和持久性很好理解,原子性:事务中的操做必须都成功才算成功。即把他当成一个整体。持久性:即对数据的操做一旦提交,对数据库的数据改变就是永久的。
接下来说说隔离性和一致性。这两个要放在一起说。他两是此消彼长的两个属性,亦其中一个的增长代表另一个的到下降。
隔离性是为了去解决数据库并发操做时发生的一些问题。即,脏读,不可重复读,幻读。
这里隔离级别分了4级:
脏读:出现这种情况,隔离级别为读未提交,t1修改数据库数据,但未提交,这时t2读取i的数据,读取到了t1未提交的修改数据。这是不能允许的错误,类比:你要给人赚钱,就差输密码转账了,但这时对面直接有了你要转的钱。银行亏死。
不可重复读:T2第一次读时i = 5000(未提交),这时T1修改数据库i值,T2再读i值改变。不过并不会有什么大问题,oracle数据的默认隔离级别就是读已提交,这个级别可以出现不可重复读.
幻读:T2现读数据库,只用i = 5000(未提交),这时T1给数据库相应表增加b = 60.
然后T2再读,就是i = 5000,b = 60.在一次事务中,出现两种结果,像幻觉一样.
总结一下:
必须要消除脏读,不可重复读和幻读看情况.四个隔离级别,读已提交消除的是脏读,可重复读消除的如名.串行化消除的是幻读,这几个隔离级别是一个一个迭代出来的.就是说,是在上一个隔离级别的基础上进一步去完善一致性.
一致性就像多线程操做的共享资源,共享资源为了同步几个线程的共享资源的一致性,需要加锁等操作,类比一下.就会明白随着一致性的提高数据库的并发性就会下降.这里不过表现为隔离级别的上升.
注意
Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED 。
Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。
在MySql中设置隔离级别
每启动一个 mysql 程序, 就会获得一个单独的数据库连接.
@@tx_isolation, 表示当前的事务隔离级别。
查看当前的隔离级别:
SELECT @@tx_isolation;
设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
注意设置了数据库系统的全局的隔离级别后想回归默认需要重启服务.
JDBC事务处理
首先明确数据操做一旦提交,就无法回滚。
数据提交的时机:
- 一个连接的对象被创建时,默认是自动提交事务的。无法回滚。
- 关闭数据库连接,数据会自动提交
为了使多条SQL语句作为一个事务来执行。需要调用Connection对象的setAutoCommit(false);取消自动提交操做,在执行完所有操做后使用commit();提交事务,在这期间如果发生异常就进行回滚事务操做rollback()。
这些操做会导致数据的自动提交
-
DDL操做一旦执行,都会自动提交
-
set autocommit = false 对DDL操做失效
-
DML默认情况下,一旦执行,就会自动提交。
-
可以通过set autocommit = false的方式取消DML的自动提交
-
默认在关闭连接时,会自动提交数据
具体的实现原理就是,向增删改操做方法参数种放入同一个连接对象,并且不再操做方法种关闭连接,仅在总的调用处进行关闭和回滚处理。
实例代码:
sql具体的执行逻辑。
//通用增删改操做-------version2.0(考虑事务)
public int updataTest1(Connection conn,String sql,Object ...args){//sql语句中占位符个数与可变形参个数必须相同
PreparedStatement ps = null;
try {
//1.加载并注册驱动获取数据库的连接
// conn = JdbcUtils.getConnection();
//2.预编译SQL语句返回PrepareStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//4.执行
return ps.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//5.关闭资源
JdbcUtils.closeResource(null, ps);
}
return 0;
}
调用上述方法实现将多条SQL语句组合成事务。
@Test
public void test1(){
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
//1. 取消数据的自动提交
System.out.println(conn.getAutoCommit());
conn.setAutoCommit(false);
String sql1 = "update user_table set balance = balance - 100 where user = ?";
updataTest1(conn,sql1, "AA");
//模拟网络异常
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ?";
updataTest1(conn,sql2, "BB");
System.out.println("success!");
//2,提交数据
conn.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
//3.回滚数据
try {
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}finally {
//在数据库连接池中,一定要回复DML的自动提交功能
//主要针对数据库连接池
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JdbcUtils.closeResource(conn, null);
}
}