概念
什么是事务?百度百科对于事务的定义如下:
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。—— [百度百科]
可以看出,事务是一个操作单元,当然,在关系型数据库系统中,事务就是一次SQL的操作,要么都成功,要么都失败。
事务特性(ACID):
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
原子性:原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生;
一致性:事务前后数据的完整性必须保持一致;
隔离性:事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离;
持久性:持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
不考虑事务的隔离性会发生什么问题?
脏读
脏读就是一个事务读取到了另一个事务未提交的数据
不可重复读
两次读取的数据不一致(update)
虚读(幻读)
两次读取的数据一致(insert)
丢失更新
两个事务对同一条记录进行操作,后提交的事务,将先提交的事务的修改的数据覆盖了
隔离级别
对于上述不考虑事务隔离性产生的四个问题我们可以通过事务的隔离级别来解决。
事务的隔离级别有哪些?
隔离级别 | 含义 |
---|---|
Serializable | 可避免脏读、不可重复读、虚读情况的发生。(串行化) |
Repeatable read | 可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读 |
Read committed | 可避免脏读情况发生(读已提交) |
Read uncommitted | 最低级别,以上情况均无法保证。(读未提交) |
如何设置事务的隔离级别?
MySQL
查看事务隔离级别
select @@tx_isolation #查询当前事务隔离级别
mysql中默认的事务隔离级别是 Repeatable read。
扩展:oracle 中默认的事务隔离级别是 Read committed
设置事务级别
set session transaction isolation level #设置事务隔离级别
JDBC
在jdbc中设置事务隔离级别可以使用java.sql.Connection接口中提供的方法
void setTransactionIsolation(int level) throws SQLException
level取以下Connection 常量之一:Connection.TRANSACTION_READ_UNCOMMITTED
Connection.TRANSACTION_READ_COMMITTED
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_SERIALIZABLE
注:不能使用 Connection.TRANSACTION_NONE,因为它指定了不受支持的事务。
丢失更新
如何解决丢失更新的问题?我们可以采用两种方式解决丢失更新。
悲观锁
悲观锁 (假设丢失更新一定会发生 ) —– 利用数据库内部锁机制,管理事务提供的锁机制,其有两种锁机制:
1、共享锁
select * from table lock in share mode(读锁、共享锁)
2、排他锁
select * from table for update (写锁、排它锁)
注:update语句默认添加排它锁
乐观锁
乐观锁 (假设丢失更新不会发生)——- 采用程序中添加版本字段解决丢失更新问题,在数据表添加版本字段。每次修改过记录后,版本字段都会更新,如果读取是版本字段,与修改时版本字段不一致,说明别人进行修改过数据 (重改) 。
create table product (
id int(8),
name varchar(20),
updatetime timestamp
);insert into product values(1,’冰箱’,null);
update product set name=’洗衣机’ where id = 1;
演示
脏读
一个事务读取到另一个事务的为提交数据
设置A,B事务隔离级别为Read uncommitted
set session transaction isolation level read uncommitted;
1.在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事务中
start transaction;
select * from account;
这时,B事务读取时,会发现,钱已经汇完。那么就出现了脏读。
当A事务提交前,执行rollback,在commit, B事务在查询,就会发现,钱恢复成原样
也出现了两次查询结果不一致问题,出现了不可重复读.
解决脏读问题
将事务的隔离级别设置为 read committed来解决脏读
设置A,B事务隔离级别为 Read committed
set session transaction isolation level read committed;
1.在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事务中
start transaction;
select * from account;
这时B事务中,读取信息时,是不能读到A事务未提交的数据的,也就解决了脏读。
让A事务,提交数据 commit;
这时,在查询,这次结果与上一次查询结果又不一样了,还存在不可重复读。
解决不可重复读
将事务的隔离级别设置为Repeatable read来解决不可重复读。
设置A,B事务隔离级别为Repeatable read;
set session transaction isolation level Repeatable read;
1.在A事务中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb';
2.在B事务中
start transaction;
select * from account;
当A事务提交后commit;B事务在查询,与上次查询结果一致,解决了不可重复读。
设置事务隔离级别 Serializable ,它可以解决所有问题
set session transaction isolation level Serializable;
如果设置成这种隔离级别,那么会出现锁表。也就是说,一个事务在对表进行操作时,其它事务操作不了。
总结
脏读:一个事务读取到另一个事务为提交数据
不可重复读:两次读取数据不一致(读提交数据)—update
虚读:两次读取数据不一致(读提交数据)—-insert
事务隔离级别:
Read Uncommitted 什么问题也解决不了.
Read Committed 可以解决脏读,其它解决不了.
Repeatable Read 可以解决脏读,可以解决不可重复读,不能解决虚读.
Serializable 它会锁表,可以解决所有问题.
安全性
Serializable > Repeatable Read > Read Committed > Read Uncommitted
性能 :
Rerializable < Repeatable Read < Read Committed < Read Uncommitted
实际开发中,通常不会选择 Serializable 和 Read Uncommitted ,MySQL默认隔离级别是Repeatable Read ,Oracle默认隔离级别是Read Committed