数据库事务介绍
-
事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。 -
事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(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 再次读取同一个表, 就会多出几行。
-
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。 -
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
四种隔离级别
| 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';
-
授予权限
grant all privileges on *.* to tom@'%' identified by 'abc123';
grant select,insert,delete,update on db_demo.* to tom@localhost identified by 'abc123';
JDBC事务处理
案例:用户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);
String sqlstr1 = "update user_assets set assets = assets - 100 where user_name = ?";
ps = conn.prepareStatement(sqlstr1);
ps.setObject(1,"A");
ps.execute();
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();
try{
conn.rollback();
}catch (SQLException sqlerr){
sqlerr.printStackTrace();
}
}finally {
try{
conn.setAutoCommit(true);
}catch (SQLException e){
e.printStackTrace();
}
JDBCUtils.CloseResource(conn,ps);
}
}
}
|