个人网站文章地址
一、事务的ACID属性
1.原子性(Atomicity)
事务的操作要么同时执行成功,要么同时执行失败。比方说:
sql = “update user set money = money - 1000 where name = ‘Tom’”;
sql = “update user set money = money + 1000 where name = ‘weder’”;
以上为用户Tom给weder转1000块的操作,要么两条sql同时成功,要么同时失败,是不可分割的(原子)。
2.一致性(Consistency)
一个事务操作前后的数据是一致的。比方说:
用户weder与用户Tom相互转钱后,他们两个人的总金额(共:2000)与没相互转钱之前(共:2000)是相同的(一致的),只是占有不同而已。
3.隔离性(Isolation)
前提是在并发环境中,事务之间是不会相互干扰的(隔离),比方说:
两个事务同时对用户weder的金额进行操作,它们之间是不会产生干扰的。
显然,隔离性会出现问题:
①脏读:一个事务(A)读到了另一个事务(B)未提交前的数据(一旦B事务执行了回滚操作,A读到的数据(脏的数据)就无效了)
②不可重复读:事务中,前后两次查询的数据不一致(第一次查到金额为1500,再查一次就变成了1000(有事务进行了提交))
③幻读:事务中,前后两次查询的数据行不一致(第一次查出现了用户Tom与用户weder的数据,第二次查多出了个用户Jack的数据)
4.持久化(Durability)
事务一旦提交,就会保存(持久化到)在数据库中,就算数据库出现问题宕机,重启后,数据一定是回到事务已提交的状态。
二、事务的隔离级别
1.读未提交
事务可以读到其它事务未提交的内容,一般不采用
2.读已提交
只能读到其它事务已提交的内容,防止了脏读
3.可重复读
是MySQL的默认隔离级别,让事务每次读到的数据都一致,防止了脏读、不可重复读
4.串行化
是最高的隔离级别,每一个事务都要串行(一个接一个)执行,防止了脏读、不可重复读、幻读。但是这样也会导致效率极低。
三、使用JAVA实现MySQL中的事务
数据库建立:其中将money设置为无符号(为了不出现负数的值,同时也为了用来测试事务)
CREATE DATABASE test;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) ,
`money` int(255) UNSIGNED ,
PRIMARY KEY (`id`)
)
INSERT INTO `user` VALUES (1, 'weder', 1000),(2, 'Tom', 1000);
效果:
Java代码目录
Connect类:用作连接数据库,并封装好查询数据库、关闭连接的方法
public class Connect {
//定义连接数据库参数
static final String url = "jdbc:mysql://localhost:3306/test?useSSL=false";
static final String user = "root";
static final String passWord = "123456";
//连接类
public static Connection connection() throws SQLException, ClassNotFoundException {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//返回连接类
return DriverManager.getConnection(url, user, passWord);
}
//查询数据库信息类
static void sele(PreparedStatement stat,Connection con) throws SQLException {
//执行查询表信息的sql
String sql = "select * from user ";
stat = con.prepareStatement(sql);
//执行sql语句后返回结果集
ResultSet rs = stat.executeQuery();
// 展开结果集数据
while(rs.next()){
// 通过字段检索
String name = rs.getString("name");//获取user_name值
String money = rs.getString("money");//获取sex值
// 输出数据
System.out.print("名字: " + name);
System.out.println(" 余额: " + money);
}
rs.close();
}
//关闭连接释放资源
static void closeConnect(PreparedStatement stat,Connection con) throws SQLException {
stat.close();
con.close();
}
}
UseTest测试类:用于测试事务,实现Tom用户给weder用户转1000
public class UseTest {
public static void main(String[] args) throws Exception {
//加载驱动并获取连接
Connection con = Connect.connection();
//初始化PreparedStatement
PreparedStatement stat = null;
//初始化sql语句
String sql;
//执行查询语句
System.out.println("执行事务前:");
Connect.sele(stat,con);
//执行事务
try {
//先停止自动提交(try方法块的内容可以同时执行),使用rollback()回滚操作必须关闭
con.setAutoCommit(false);
//weder用户获得了1000块
sql = "update user set money = money + 1000 where name = 'weder'";
stat = con.prepareStatement(sql);
stat.executeUpdate();
//Tom用户失去了1000块
sql = "update user set money = money - 1000 where name = 'Tom'";
stat = con.prepareStatement(sql);
stat.executeUpdate();
//开启自动提交
con.setAutoCommit(true);
}catch (SQLException e){
System.out.println("错误信息:" + e);
//出现问题则使数据回滚,回到执行事务前的数据状态
con.rollback();
}
//执行查询语句
System.out.println("执行事务后:");
Connect.sele(stat,con);
//释放资源
Connect.closeConnect(stat,con);
}
}
第一次运行:双方都有1000,所以事务执行成功
第二次运行:Tom用户余额为0,不能执行money = money-1000的操作(money被设置为无符号,即不能为负号),故会执行失败,即使前面weder用户已经有获得1000块的操作,但是事务未提交,会回滚到未执行事务前的状态(原子性:同时成功,同时失败)
以上为个人对于事务的理解,如果有什么纰漏,希望可以帮忙指正~