MySql事务
事务的概述:
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。
事务案例:
A–B转账,有以下两条sql语句:
update count set money=money+100 where name=’aaa’;
update count set money=money-100 where name=’bbb’;
其中一条sql语句失败,则在此事务内的其他sql语句全部失败,要么一起成功,要么一起失败
Mysql中的事务
a、mysql引擎是支持事务的
b、mysql默认自动提交事务。每条语句都处在单独的事务中。
c、手动控制事务
开启事务:start transaction | begin conn.setAutoCommit(false);
提交事务: commit conn.commit();
回滚事务: rollback conn.rollback();
JDBC事务
JDBC事务是由Connection对象所控制的,它提供了两种事务模式:自己主动提交和手动提交,默认是自己主动提交。
自己主动提交就是:在JDBC中。在一个连接对象Connection中。默认把每一个SQL语句的运行都当做是一个事务(即每次运行完SQL语句都会马上将操作更新到数据库)。
手动提交就是:当须要一次性运行多个SQL语句,将多个SQL语句组成一个事务(即要么都成功,要么回滚全部的操作)时,就得手动提交。
在命令行中观察主动提交:
进入mysql:
mysql -u 用户名 -p 密码
进入holiday数据库:
use holiday;
在mysql直接写一个sql语句(所以是默认自动提交的):
执行前
执行后:
在命令行中观察手动提交:
开启事务:
start transaction|begin
因为开启事务后并没有commit(提交),所以即使内存中数据变化了,但是没有提交,并没有存盘,所以表的数据依然没有变化!
在JDBC中实现事务:
代码:
没有手动控制事务(执行事务前):
代码:
package commons;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestTransaction {
public static void main(String[] args){
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DBCPUtils.openConnection();
ps = conn.prepareStatement("update count set money=money-100 where name='aaa'");
ps.execute();
int i=10/0;//在两条sql语句中间出现异常
ps = conn.prepareStatement("update count set money=money+100 where name='bbb'");
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBCPUtils.closeConnection(conn,ps,null);
}
}
}
执行结果:
aaa执行了sql语句,bbb没执行
aaa给bbb转账100元,aaa少钱了,bbb的钱确没多!
修改后,手动控制事务,一个事务中的所有sql语句失败一起失败,成功一起成功
conn.setAutoCommit(false);//相当于开启事务 begin
conn.commit();//相当于 提交事务
conn.rollback();//回滚事务 rollback
代码:
package commons;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestTransaction {
public static void main(String[] args){
Connection conn = null;
PreparedStatement ps = null;
try {
conn = DBCPUtils.openConnection();
conn.setAutoCommit(false);//相当于开启事务 begin
ps = conn.prepareStatement("update count set money=money-100 where name='aaa'");
ps.execute();
int i=10/0;//在两条sql语句中间出现异常
ps = conn.prepareStatement("update count set money=money+100 where name='bbb'");
ps.execute();
conn.commit();//相当于 提交事务
} catch (SQLException e) {
e.printStackTrace();
try {
conn.rollback();//只要出现异常 回滚事务 rollback
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
DBCPUtils.closeConnection(conn,ps,null);
}
}
}
事务的特性(面试题)
原子性:事务是一个整体,事务中的操作要么都发生,要么都不发生;
一致性:事务必须要使数据库从一个一致性状态转换到另外一个一致性状态,即转账的总金额不变;
隔离性:多个用户并发访问数据库时,多个并发事务之间要相互隔离,谁也别影响谁;
持久性:指一个事务一旦被提交,它对数据库的改变就是永久的,即使数据库发生故障也不应该对其有任何影响。即提交后必须存盘。
事务的隔离级别
1.脏读:用户当前操作的事务读到了另一个事务未提交的数据
例子:
我现在正在取钱,账户里有500,我没有提交取钱的这个事务的时候,一哥们正好现在要给我转账,他给我转300,但他也没有提交,就给我打了电话,老王我给你转账了300,我一看账户当前余额800,我说好的。然后这哥们啪,rollback了,我一看账户500……
这就是读到了别人没有提交的脏数据
2.不可重复读和虚读(幻读)很类似,一个是由update导致的,一个是由insert导致的
2.1 不可重复读:我当前操作的事务还没完成提交,就读取到了另一个事务提交到的数据。(update)
2.2 虚读(幻读):我当前操作的事务还没完成提交,就读取到了另一个事务插入提交的数据。(insert)
数据库通过设置事务的隔离级别防止以上情况的发生:(有可能发生,不是一定发生)
* 1、READ UNCOMMITTED: 赃读、不可重复读、虚读都有可能发生。
* 2、READ COMMITTED: 避免赃读。不可重复读、虚读都有可能发生。(oracle默认的)
* 4、REPEATABLE READ:避免赃读、不可重复读。虚读有可能发生。(mysql默认)
* 8、SERIALIZABLE: 避免赃读、不可重复读、虚读。
级别越高,性能越低,数据越安全
mysql中:
查看当前的事务隔离级别:SELECT @@TX_ISOLATION;
更改当前的事务隔离级别:SET TRANSACTION ISOLATION LEVEL 四个级别之一。
设置隔离级别必须在事务之前
一、事务隔离级别为read uncommitted
脏读,不可重复读,虚读都有可能发生
查看mysql默认事务隔离级别:
设置当前事务隔离级别 (在开启事务之前设置) read uncommmitted(脏读 不可重复读 虚读都有可能发生;因为读取到了没有提交的数据):
进入数据库,读取当前数据:
开启另一个事务(线程)操作同一个数据,但是没有提交:
结果读取到脏数据,没有提交,另一个事务却读取到了提交后的数据:
哥们我转账给你了啊,却没提交,rollback后,一脸懵逼:
二、事务隔离级别为read committed
避免了脏读,但是不可重复读和虚读都有可能发生
发生不可重复读:
设置事务隔离级别为read committed后
开启事务A,查询count表
再开启一个事务update B,并commit
事务A还没commit就可查询到事务B提交的数据
会导致事务A的查询的数据一致变化,干扰到了事务A
发生虚读:
设置事务隔离级别为read committed后
开启事务A,查询count表
再开启一个事务Binsert,并commit
事务A还没commit就可查询到事务B提交的数据
会导致事务A的查询的数据一致变化,干扰到了事务A
三、事务隔离级别为repeatable read
避免脏读 不可重复读 虚读可能发生
四、事务隔离级别为serializable
避免脏读 不可重读读 虚读 (最高级别)
只要当前事务不commit ,其他事务都不会执行
JDBC控制事务的隔离级别
READ UNCOMMITTED 等级: 1
READ COMMITTED 等级:2
REPEATABLE READ 等级:4
SERIALIZABLE 等级:8
如何在java程序中控制事务的隔离级别;
在开启事务前设置:
Connection.setTransactionIsolation(int level);
但是一般不用写
因为Oracle mysql已经自动设置好了级别