mysql中事务ACID特性
ACID
概述
事务具有4个特征,分别是原子性、一致性、隔离性和持久性,简称事务的ACID特性;
详述
1、原子性(atomicity)
一个事务执行的过程是原子性的,即不可分割。要么全部提交成功,要不全部失败回滚。不能是只执行其中的部分操作,它本身包含的一组操作被看做一个整体不可分割
例如:一个事物中包含一组操作:
A=100;B=1
a、A=A-1;b、B=B+1;
事务操作完毕,a、b要么都执行(A=99,B=2),要么都不执行(A=100,B=1)
2、一致性(consistency)
为了保证数据库的完整性和一致性。事务提交前后数据保持一致。
例如:
A、B总和为101,无论怎么操作,都要保证最后A、B的总和为101
3、持久性(durability)
事务的提交时永久性的。一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。
–即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态
4、隔离性(isolation)
事务的隔离级别(低–>高):
- Read Uncommitted:最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。可能会导致脏读、幻读或不可重复读
- Read Committed:只有在事务提交后,其更新结果才会被其他事务看见。== 可以解决脏读问题==。
- Repeated Read:在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。 可以解决脏读、不可重复读。
- Serialization:事务串行化执行,隔离级别最高,牺牲了系统的并发性。 可以解决并发事务的所有问题。
隔离性体现在多个事务并发情况,并发事务之间互相影响的程度,在事务并发操作时,可能出现的问题有:
脏读:
事务1修改了数据(A=A-100),但没有提交,事务2读取了事务1没有提交的数据(A:900),如果此时1去提交并提交失败数据回滚到之前的值(A:1000),事务2读到的就是脏数据(A:900)
- 解决方案:Read Committed,
将事务的隔离级由 Read Uncommitted升级到 Read Committed,
允许读取并发事务已经提交的数据(只有在事务提交后,其更新结果才会被其他事务看见),可以阻止脏读,但是幻读或不可重复读仍有可能发生
不可重读:
同一个事务两次读取同一份的数据但读取的结果不一致。 不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这回导致锁竞争加剧,影响性能
事务1第一次读取到数据A(A=1000),此时事务2对数据A进行操作(A=A-100),然后提交事务成功,此时数据A=900,事务1继续去读取数据A ,发现数据A变成了900,与之前读到的1000值不一致了。
- 解决方案:Repeated Read
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
幻读:
在同一个事务中,同一个查询多次返回的结果不一致。 幻读是由于并发事务增加记录导致的,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。
事务2先执行一次查询操作,查到此时表中有两条记录(A、B),事务1此时执行一次插入数据操作并提交成功,随后事务二再执行一次查询操作,此时查到表中有3条记录(A、B、C),比第一次查询多了一条记录
- 解决方案: Serialization
最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
脏读与不可重读区别:
脏读是两个事务间,事务1读取了事务2没有提交的数据,不可重读是同一个事务,事务1多次读取同一数据不一致,这是由于查询间隔期,该数据被另一个事务2修改并提交了。
幻读与不可重读区别:
幻读与不可重读都是读取了另一个事务已提交的数据,不同的是不可重读多次查询的通一数据项(account表name为A的money),针对的是对同一行数据的update或delete,而幻读是多次查询的数据记录(account表的所有记录),针对insert或delete操作
案例
1、mysql工具类
public class JdbcUtils {private static String driver;private static String url;private static String username;private static String password;static {try{InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");Properties properties = new Properties();properties.load(in);driver=properties.getProperty("driver");url=properties.getProperty("url");username=properties.getProperty("username");password=properties.getProperty("password");Class.forName(driver);} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url,username,password);}public static void release(Connection conn, Statement stmt, ResultSet rs){if(rs!=null){try {rs.close();} catch (SQLException throwables) {throwables.printStackTrace();}}if(stmt!=null){try {stmt.close();} catch (SQLException throwables) {throwables.printStackTrace();}}if(conn!=null){try {conn.close();} catch (SQLException throwables) {throwables.printStackTrace();}}}}
2、数据库数据

3、执行转账成功操作
package com.phx.test;import com.phx.utils.JdbcUtils;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 pstm=null;try {conn= JdbcUtils.getConnection();//关闭数据库的自动提交,自动开启事务conn.setAutoCommit(false);String sql1="update account set money=money-100 where name='A'";pstm=conn.prepareStatement(sql1);pstm.executeUpdate();String sql2="update account set money=money+100 where name='B'";pstm=conn.prepareStatement(sql2);pstm.executeUpdate(sql2);//业务完毕,提交事务conn.commit();} catch (SQLException throwables) {try {//失败则回滚conn.rollback();} catch (SQLException e) {e.printStackTrace();}throwables.printStackTrace();}finally {JdbcUtils.release(conn,pstm,null);}}
}
执行成功,持久化到数据库中

4、执行转账失败操作

A执行完A=A-100成功后由于异常没有执行B(及B执行失败)所以会执行rollback(),不会提交到数据库

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
