数据库事务介绍
-
事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
-
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务**回滚(rollback)**到最初状态。
-
为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
事务的ACID属性
名称 | 简介 | 举例 |
---|---|---|
Atomicity原子性 | 所有的SQL语句要么全部成功,要么全部失败,不会存在部分更新。 | 假设有以下场景,A转账100元给B。这里有两个动作:一是A账号减少100元,二是B账号增加100元,这两个动作不可分割。 |
Consistency一致性 | 事务只能以允许的方式改变受其影响的数据。 | 假设A和B两者的钱加在一起一共100元,那么无论A和B之间如何转账,转几次账,事务结束后两个用户的钱加起来一定还是100元。 |
Isolation隔离性 | 同时发生的事务(并发事务)不应该导致数据库出于不一致的状态中。系统中每个事务都应该像唯一事务一样执行。任何事务都不应影响其他事务的存在。 | 隔离性即是要达到这样一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。 |
Durability持久性 | 无论数据库或系统是否发生故障,数据都会永久保存在磁盘上,并且不会丢失。 | 当开发人员在使用JDBC操作数据库时,在提交事务后,提示用户事务操作完成,那么这个时候数据就已经存储在磁盘上了。即使数据库重启,该事务所做的更改操作也不会丢失。 |
数据库的并发问题
-
对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
-
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
-
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
四种隔离级别
- 数据库提供的4种事务隔离级别:
Read Uncommitted | Read Committed | Repeatable Read | Serializable | |
---|---|---|---|---|
中文名 | 未提交读,读取未提交内容 | 提交读,读取提交内容 | 可重复读 | 可串行化、序列化 |
简介 | 在该隔离级别,所有的事务都可以看到其他未提交事务的执行结果,即在未提交读级别中,数据的修改,对其他事务也都是可见的,该隔离级别很少用于实际应用。读取未提交的数据,也被称为脏读。该隔离级别最低,并发性能最好。 | 一个事务只能看到已经提交事务所做的改变,换句话说,一个事务从开始到提交之前,所做的任何修改对其他事务都是不可见的。这是大多数数据库系统的默认隔离级别。 | 可重复读可以确保同一个事务,在多次读取同样的数据的时候,得到同样的结果。它解决了脏读的问题,不过理论上,这会导致另一个问题:幻读。 | 这是最高的隔离级别,它通过强制事务排序,强制事务串行执行,使之不可能相互冲突,从而解决幻读问题。换言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。实际应用中也很少用到这种隔离级别,只有在非常需要确保数据一致性而且可以接受没有并发的情况下,才考虑用该级别。这是花费代价最高但是最可靠的事务隔离级别。 |
脏读 | 支持 | |||
不可重复读 | 支持 | 支持 | ||
幻读 | 支持 | 支持 | 支持 | |
默认级别数据库 | Oracle、SQL Server | MySQL | ||
并发性能 | 最高 | 比Read Uncommitted低 | 比Read Committed低 | 最低 |
-
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;
-
补充操作:
-
创建mysql数据库用户:
create user tom identified by 'abc123';
-
授予权限
#授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123. grant all privileges on *.* to tom@'%' identified by 'abc123'; #给tom用户使用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。 grant select,insert,delete,update on db_demo.* to tom@localhost identified by 'abc123';
-
JDBC事务处理
-
数据一旦提交,就不可回滚。
-
数据什么时候意味着提交?
- 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
- **关闭数据库连接,数据就会自动的提交。**如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
-
JDBC程序中为了让多个 SQL 语句作为一个事务执行:
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。
案例:用户A向用户B转账100元
import utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTransaction {
public static void main(String[] args) {
JDBCTransactionDemo();
}
public static void JDBCTransactionDemo(){
Connection conn = null;
PreparedStatement ps = null;
try{
conn = JDBCUtils.GetConnection();
// 开启事务,关闭自动提交
conn.setAutoCommit(false);
// A的账户减少100
String sqlstr1 = "update user_assets set assets = assets - 100 where user_name = ?";
ps = conn.prepareStatement(sqlstr1);
ps.setObject(1,"A");
ps.execute();
// B的账户增加100
String sqlstr2 = "update user_assets set assets = assets + 100 where user_name = ?";
ps = conn.prepareStatement(sqlstr2);
ps.setObject(1,"B");
ps.execute();
// 如果没有异常,则提交事务
conn.commit();
}catch (Exception e){
e.printStackTrace();
// 5.若有异常,则回滚事务
try{
conn.rollback();
}catch (SQLException sqlerr){
sqlerr.printStackTrace();
}
}finally {
try{
// 恢复每次DML操作的自动提交功能
conn.setAutoCommit(true);
}catch (SQLException e){
e.printStackTrace();
}
JDBCUtils.CloseResource(conn,ps);
}
}
}