简介
一组要么同时执行成功,要么同时失败的SQL语句。是数据库操作的一个不能分割的执行单元。
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
事务开始于:
连接到数据库上,并执行一条DML语句insert、update或delete
前一个事务结束后,又输入了另一条DML语句
事务结束于:
执行commit或rollback语句。
执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
执行一条DCL语句,例如grant语句,在这种情况下,会自动执行commit。
断开与数据库的连接。
执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语句。
事务的四大特性
ACID
Atomicity(原子性):表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败
Consistency(一致性):表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态
Isolation(隔离性):事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
Durability(持久性):持久性事务完成之后,它对于系统的影响是永久性的。
- 案例:
// 正常转账
public static void main(String[] args) throws Exception{
Connection conn = DbUtils.getConnection();
Statement stat = conn.createStatement();
//转出(账户1)
stat.executeUpdate("update account set money = money-1000 where id=1;");
//转入(账户2)
stat.executeUpdate("update account set money = money+1000 where id=2;");
stat.close();
conn.close();
}
//转入转出都正常执行
// 转账时发生异常
public static void main(String[] args) throws Exception{
Connection conn = DbUtils.getConnection();
Statement stat = conn.createStatement();
//转出(账户1)
stat.executeUpdate("update account set money = money-1000 where id=1;");
//模拟断电 --> java.lang.ArithmeticException: / by zero
int i = 10/0;
//转入(账户2)
stat.executeUpdate("update account set money = money+1000 where id=2;");
stat.close();
conn.close();
}
//转出执行,转入因异常不执行,此时就出现了重大数据错误
//用事务处理异常
public static void main(String[] args){
Connection conn =null;
Statement stat = null;
try {
conn = DbUtils.getConnection();
//开启事务(阻止事务的自动提交)
conn.setAutoCommit(false);
stat = conn.createStatement();
//转出
stat.executeUpdate("update account set money = money-1000 where id=1;");
//断电
int i = 10/0;
//转入
stat.executeUpdate("update account set money = money+1000 where id=2;");
//提交
conn.commit();
} catch (Exception e) {
e.getMessage();
try {
conn.rollback();
conn.commit();//可加也不不加
System.out.println("转账失败");
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
DbUtils.closeAll(conn, stat, null);
}
}
//转入转出要么都执行,要么都不执行,即相当于在应用层面达到事务的一致性
ACID中的一致性是指完整性约束不被破坏,完整性包含实体完整性(主属性不为空)、参照完整性(外键必须存在原表中)、用户自定义的完整性。
- Mysql支持的事务语句 :
#开启事务
START TRANSACTION; # connection.setAutoCommit(false);
UPDATE account SET money=money-1000 WHERE id=1;
UPDATE account SET money=money+1000 WHERE id=2;
#提交事务
COMMIT;#connection.commit();
#回滚
ROLLBACK; #connection.rollback();
事务隔离级别
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读取未提交内容):在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容):这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别及以下隔离级别出现不可重复读(Nonrepeatable Read)问题,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read 可重读:这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻读” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制(至少在该RR级别)解决了部分(读取时)该问题。
Serializable 可串行化 :这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。效率最低的。
oracle:只支持了 读提交(默认) 和 序列化读
mysql:都支持了 可重复读(默认)
隔离级别 低 -> 高:并发性降低,安全性提高
信息源共享的数据越来越少,造成事务彼此阻塞的几率越来越大
这四种隔离级别采取不同的锁类型来实现。
#修改事务的隔离级别:
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL
[READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE];
#查看事务隔离级别
SELECT @@tx_isolation;
事务并发时的安全问题:
1>脏读:一个事务中 读到了其他事务中 未提交的数据
2>不可重复读:一个事务中 多次读取相同的数据行,但是,结果不一致
3>幻影读:一个事务中 多次读取同一张表,但是,数据行数不一致;
查询时没有某数据,但是操作时,却提示存在此数据。(在某列唯一情况下事务2读取数据,此时事务1插入一行数据,事务2也想插入此行数据时失败)
- 读未提交 --> 脏读(Drity Read):
某个事务已更新一份数据未提交前,另一个事务在此时读取了同一份数据
# A方 买 张三
SELECT @@tx_isolation;
START TRANSACTION;
UPDATE account SET money=money-2000 WHERE id=1;
UPDATE account SET money=money+2000 WHERE id=2;
COMMIT;
ROLLBACK;
# B方 卖 李四
#(修改隔离级别)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#查看账户数据(脏读)
SELECT * FROM account;
#发货
- 读提交 --> 不可重复读(Non-repeatable read):
在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
#修改隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM account;
#统计所有账户总数据(不可重复读)
START TRANSACTION;
SELECT SUM(money) FROM account;
SELECT SUM(money) FROM account;
SELECT SUM(money) FROM account;
COMMIT;
- 可重读(Repeatable Read) --> 幻读:
在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。但是InnoDB存储引擎通过多版本并发控制机制解决了该问题。
#再次修改隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
#统计所有账户总数据(可重读)
START TRANSACTION;
SELECT SUM(money) FROM account;
SELECT SUM(money) FROM account;
SELECT SUM(money) FROM account;
COMMIT;
- 可串行化(Serializable) :
悲观锁,强制事务排序,使之不可能相互冲突,从而解决幻读问题。