事务
一、什么是事务?
1. 事务的概念
- 事务(TRANSACTION)是作为单个逻辑工作单元的一系列操作。
- 多个操作作为一个整体向系统提交,要么都执行,要么都不执行。事务是一个不可分割的工作逻辑单元。
2. 事务的四大特性(ACID)
- 原子性(Actomicity)
- 事务时一个完整的操作,各步操作是不可分的,要么都执行,要么都不执行。
- 一致性(Consistency)
- 当事务完成时,数据必须处于一致状态
- 隔离性(Isolation)
- 并发事务之间彼此隔离、独立,它不应以任何方式依赖于或影响其他事务。
- 持久性(Durability)
- 事务完成后,它对数据库的修改被永久保持。
二、事务操作
MySQL中支持事务的存储引擎有InnDB和BDB
-
创建事务
- 开启事务:
begin;
或start transaction;
- 设置回滚点:
savepoint sp;
- 回滚(撤销事务):
rollcack ;
- 提交事务:
commit;
示例:从张三的账户转出500元,存入李四的账户中
BEGIN; /*--转账:张三的账户减少500元,李四的账户增加500元--*/ UPDATE `bank` SET `currentMoney`=`currentMoney`-500 WHERE `customerName`='张三'; ## rollback; UPDATE `bank` SET `currentMoney`=`currentMoney`+500 WHERE `customerName`='李四'; COMMIT; SELECT * FROM bank;
- 开启事务:
-
自动开启和关闭事务
- 默认情况下,每条单独的SQL语句视为一个事务
- 关闭默认提交状态后,可手动开启、关闭事务
- 语法:
set autocommit = 0|1
- 关闭自动提交后,从下一条SQL语句开始则开启新事务,需使用COMMIT或ROLLBACK语句结束该事务。
示例:
SET autocommit=0; /*--转账:张三的账户减少500元,李四的账户增加500元--*/ UPDATE `bank` SET `currentMoney`=`currentMoney`-500 WHERE `customerName`='张三'; UPDATE `bank` SET `currentMoney`=`currentMoney`+500 WHERE `customerName`='李四'; COMMIT; UPDATE `bank` SET `currentMoney`=`currentMoney`-1000 WHERE `customerName`='张三'; ROLLBACK; SET autocommit = 1;
三、事务的隔离性
-
什么是事务的隔离性?
隔离性是指: 并发事务之间互相影响的程度。 -
隔离性引起的问题
- 脏读: 读取未提交的数据。事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,如果事务A提交失败,事务B读到的就是脏数据。
时序 事务1 事务2 结果 1 set transaction isolation level read uncommitted;(设置隔离级别) set transaction isolation level read uncommitted;(设置隔离级别) 2 begin;(开启事务) begin;(开启事务) 3 查询语句1 结果1 4 更新语句1 ok 5 查询语句1 结果2 6 查询语句1 结果2 - 不可重复读: 同一事务中,两次读取同一条数据不一致。比如,事务B在事务A提交前读到的结果,和事务A提交后的结果可能不同。不可重复读出现的原因就是事务并发修改记录。
时序 事务1 事务2 结果 1 set transaction isolation level read committed;(设置隔离级别) set transaction isolation level read committed;(设置隔离级别) 2 begin; begin; 3 查询语句1 结果1 4 更新语句1 ok 5 查询语句1 结果2 6 commit;(提交事务) 7 查询语句1 结果2 - 虚读(幻读): 同一事务中,读取到了另一个事务新插入的数据。事务A新增了一条记录,事务B在事务A提交前后各执行了一次查询操作,发现后一次比前一次多了一条记录。幻读是由于并发事务增加记录导致的。
时序 事务1 事务2 结果 set global transaction isolation level repeatable read; set global transaction isolation level repeatable read; 1 begin; begin; 2 查询语句1 结果1 3 插入语句1 ok 4 查找语句1 结果2 5 commit; 6 查询语句1 结果2 -
隔离性问题的解决
- 隔离命令(默认为session)
- 查看隔离级别
select @@tx_isolation;
- 设置隔离级别
set [global/session] transaction isolation level 隔离级别
- 查看隔离级别
- 脏读问题的解决
set transaction isolation level read committed;
- 不可重复读的解决
set transaction isolation level repeatable read;
- 虚读的解决
set transaction isolation level serializable
- 隔离命令(默认为session)
-
事务隔离与安全效率
脏读 不可重复读 虚读 共享锁 排它锁 read uncommitted × × × 写 read committed √ × × 写 repeatable read √ √ × 写 serializable √ √ √ 读 写 安全性与效率
- 效率越高安全性越差
- 安全性:serializable>repeatable read>read committed>read uncommitted
- MySQL中默认隔离级别为:repeatable read
- Oracle中默认隔离级别为:read committed
-
共享锁与排他锁
-
共享锁(Share Lock)
共享锁又称读锁: 是读取操作创建的锁。其他用户可以并发读取用户,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
用法:SELECT ... LOCK IN SHARE MODE;
注: 在查询语句后面增加LOCK IN SHARE MODE,MySQL会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。
-
排他锁(eXclusive Lock)
排他锁又称写锁: 如果事务T对数据A加上排他锁后,则其他事务不能再对A加任何类型的锁,获准排他锁的事务既能读数据,又能修改数据。
用法:SELECT ... FOR UPDATE;
注: 在查询语句后增加FOR UPDATE,MySQL会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
-
加了共享锁可以再加共享锁,加了排他锁不能再加任何锁。
-
四、乐观锁与悲观锁
- 乐观锁和悲观锁只是一种 思想。它体现在数据库及计算机科学中的很多应用方面。
1. 更新丢失问题
时序 | 线程1 | 线程2 | 结果 |
---|---|---|---|
1 | 查询语句1 | 结果1 | |
2 | 查询语句1 | 结果1 | |
3 | 更新语句1 | ok | |
4 | 查询语句1 | 结果2 | |
5 | 更新语句2 | ok | |
6 | 查询语句1 | 结果3 | |
7 | 查询语句1 | 结果3 |
2. 问题分析
- 本质: 两个线程并发更新数据,后一个线程会覆盖前一个修改。
设定隔离级别为 serializable 可解决,但是,效率太差;
在非 serializable 隔离级别下,可使用乐观锁和悲观锁来解决此问题。
3. 乐观锁
-
乐观锁: 认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。 但是,乐观不代表不负责,那么怎么去负责多个事务顺序对数据进行修改呢?乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。不同于悲观锁,乐观锁是人为控制的。
-
原理:
- 在表中增加一个记录版本的字段;
- 在查询时不加任何锁,在修改时基于版本修改。
-
具体操作:
事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本 v1 与数据中最新的版本 v2 相对比,如果 v1 = v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时 version 会加 1,以此来表明数据已被变动。如果,v1 不等于 v2,那么说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。
- 缺点: 如果修改操作频繁的话,会造成多次修改失败,重复执行更新。
- 应用场合: 查询多,修改少。
4. 悲观锁
-
悲观锁: 悲观锁认为被它保护的数据是极其不安全的,每时每刻都有可能变动,一个事务拿到悲观锁后(可以理解为一个用户),其他任何事务都不能对该数据进行修改,只能等待锁被释放才可以执行。
-
数据库中的行锁,表锁,读锁,写锁,以及syncronized 实现的锁均为悲观锁。
-
原理:
- 在查询内容上,加上排它锁 for update;
- 使其他查询等待,等到本事务执行完毕之后,其他查询才能进行。
- 缺点: 效率较差,但比 Serializable 好。
- 应用场合: 查询少,修改多。