内容简介:范例:a给b转账100元一致性解读:不会出现a账户-100而b账户没有增加100的情况,整体的状态是一致的,总的状态不会平白无故地改变隔离性解读:在a给b转账的过程中,b进行查余额的操作,那么b得到到结果将由数据库设定得数据库隔离级别来决定。
- 原子性:一个事务要么全部成功,要么全部失败
- 一致性:事务完成以后,状态改变是一致的,一致性一般是通过结果来呈现的
- 隔离性:在不同事务试图去操作同一份数据的时候,事务之间的隔离性
- 持久性:数据提交以后,事务操作的结果才会永久保存到数据库当中
范例:a给b转账100元
一致性解读:不会出现a账户-100而b账户没有增加100的情况,整体的状态是一致的,总的状态不会平白无故地改变
隔离性解读:在a给b转账的过程中,b进行查余额的操作,那么b得到到结果将由数据库设定得数据库隔离级别来决定。
使用 sql 进行数据管理
事务1:
START TRANSACTION; UPDATE user_transaction set amount = amount -100 WHERE username = 'user1'; UPDATE user_transaction set amount = amount + 100 WHERE `username` = 'user2'; COMMIT 复制代码
事务2
SELECT * from user_transaction; 复制代码
事务3
START TRANSACTION; SELECT * from user_transaction; SELECT * FROM user_transaction WHERE username = 'user1'; COMMIT 复制代码
结果展示
- 在不修改数据库默认隔离级别的情况下,只有当执行完commit之后,其它事务才能查询到数据库的数据库的更新。
- 可重复读的展示(可重复读:一个事务中读取到的数据和事务开启的时刻是一致的) 事务3开启事务以后,开启事务1并执行事务1的第一条更新语句执行第一条查询语句,得到结果如下 执行事务1的第二条更新语句并提交事务,然后执行事务3的第2条查询语句 我们可以看到在可重读的隔离级别下,一个事务内多次读取的数据结果和事务开始时的结果是一致的,哪怕其它事务已经对原有数据进行更新并提交。
查询数据库中的设置的隔离级别,可以发现 mysql 默认的数据库隔离级别是可重读
select @@GLOBAL.tx_isolation,@@tx_isolation; 复制代码
修改数据库当前事务隔离级别为脏读
set session transaction isolation level read uncommited 复制代码
结果
- 事务1开启事务并执行第一条更新语句,事务2可以看到事务1中执行的更新的数据,即使事务并没有提交,即可以读取到脏数据。
mysql数据库的四种隔离级别
- 读未提交
- 读提交
- 可重读
- 序列读
jdbc操作事务
事务1:开启事务->两次更新->提交事务
public class JdbcTransaction { public static void main(String args[]) throws SQLException { Connection connection = getConn(); //关闭自动提交,相当于开启一个事务 connection.setAutoCommit(false); //减少账户余额 String sql1 = "UPDATE user_transaction set amount = amount -100 WHERE username = ? "; PreparedStatement ps1 = connection.prepareStatement(sql1); ps1.setString(1, "user1"); ps1.executeUpdate(); //若抛出异常,事务会回滚 //throwException(); //增加账号余额 String sql2 = "UPDATE user_transaction set amount = amount + 100 WHERE `username` = ?"; PreparedStatement ps2 = connection.prepareStatement(sql2); ps2.setString(1, "user2"); ps2.executeUpdate(); // 提交事务 connection.commit(); ps1.close(); ps2.close(); } private static Connection getConn() { String driver = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "root"; Connection conn = null; try { Class.forName(driver); //classLoader,加载对应驱动 conn = (Connection) DriverManager.getConnection(url, username, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return conn; } private static void throwException() throws SQLException { throw new SQLException(); } 复制代码
事务2: 开启事务->进行查询操作->根据查询的结果再进行更新->提交
public class JdbcTransaction2 { public static void main(String args[]) throws SQLException { Connection connection = getConn(); // 关闭自动提交,相当于开启一个事务 connection.setAutoCommit(false); // 减少账户余额,加悲观锁 String query1 = "select * from user_transaction for update"; PreparedStatement ps1 = connection.prepareStatement(query1); ResultSet resultSet = ps1.executeQuery(); Integer myAmount = 0; while (resultSet.next()){ String username = resultSet.getString(2); Integer amount = resultSet.getInt(3); System.out.println("username =" + username+ " amount = " + amount); if (username.equals("user1")){ myAmount = amount; } } // 根据查询出来的结果去更新会有什么问题? /** * 1. 开启JdbcTransaction中的事务,执行更新操作但不commit * 2. 本事务的更新操作将卡在更新数据之前,直到上一个事务提交,交出该数据锁的权限 * 3. 此时更新的数据将是根据前面查得的数据进行更新的,那么此时更新的依据将会是旧的数据 * * 解决:开启事务,锁住查询出来的数据,若其它事务正在对本事务需要查询的数据进行操作,那么本事务等待直至 * 其它事务commit */ // 如果有其它事务正在操作这条数据,那么此处将会等到其它事务提交以后才能继续往下执行 // 根据mysql的内部机制,更新同一条数据的两个事务不能同时执行,需要等待其中一个事务执行完 String sql2 = "UPDATE user_transaction set amount = ? WHERE username = ? "; PreparedStatement ps2 = connection.prepareStatement(sql2); ps2.setString(1,String.valueOf((myAmount+100))); ps2.setString(2,"user1"); ps2.executeUpdate(); System.out.println("进行数据更新"); // 提交事务 connection.commit(); ps1.close(); ps2.close(); } private static Connection getConn() { String driver = "com.mysql.jdbc.Driver"; String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "root"; Connection conn = null; try { Class.forName(driver); //classLoader,加载对应驱动 conn = (Connection) DriverManager.getConnection(url, username, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return conn; } } 复制代码
数据库:
样例示范:
执行事务1,但先不提交(断点停留在commit( ) 处),开启事务2,会发现事务2会停留在第二条sql(更新数据的sql)前,原因是事务1正在对同一份数据进行更新,事务2无法获取到数据的锁;这时提交事务1,事务2也随之提交。这时会出现的问题是事务2中是根据旧数据为依据进行更新的,这种情况在生产中是不允许出现的。 解决方式:
- 对事务2中查询出来的数据进行加锁,这里要注意的是加锁的数据一定要是我们需要查询的指定数据,所以这里一定要加上where条件,否则有可能导致锁全表,这样将给系统性能带来很大的影响。开启事务1以后,事务2直到事务1提交以后才拿到查询结果,因为要先获取该行数据的锁,这样则不会出现读旧数据去更新的情况。
以上所述就是小编给大家介绍的《MySQL 中的事务详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 详解分布式事务
- Oracle事务和对象详解
- 干货分享 | Spanner事务处理技术详解
- 关系型数据库中的事务管理详解:并发控制与事务日志
- [译] 深入剖析 Delta Lake:详解事务日志
- MySQL基础篇(06):事务管理,锁机制案例详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。