目录
什么是事务?
为什么要有事务?
使用命令行演示事务
数据准备
查看autocommit变量值
关闭autocommit自动提交
开启事务
执行sql语句
执行提交或者回滚
关于mysql和oracle数据库的提交
事务的四大特性
原子性
一致性
隔离性
持久性
事务的安全隐患
安全隐患分类
读问题
脏读
不可重复读
幻读
事务四大隔离级别
Read uncommitted 读未提交
Read committed 读已提交
Repeatable read 可重复读
Serializable 可串行化
查询和修改事务隔离级别
查询事务的隔离级别
改变连接的隔离级别
事务(Transaction)其实指的一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败。 所有的数据都回归到最初的状态(回滚)
为了确保逻辑的成功,典型例子:银行转账
将一笔钱从A->B;:一是从A账户中减去对数据库中的操作主要有两个步骤这笔钱;二是把B账户中的钱增加一笔。其实这两个步骤操作就是一个逻辑单元,就是一个操作集合,就是一个事务。这就要求两个步骤操作同步执行,要么它们都操作成功,要么都操作失败。如果一个操作失败,整个事务也就失败了,并且已经执行的操作都会被撤消,也就是回滚到先前账户不变动的状态,如果是A账户扣钱了,B账户却没增加钱,显得很不合理
-- 创建数据表
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES ('zhangsan', 1000), ('lisi', 1000);
首先我们要知道MySQL是自动提交事务的,可以查一下有关于commit的变量,sql语句如下:
show variables like'%commit%'; 这个语句不止在命令行能执行,在mysql客户端也能执行
当执行这条语句后,如下图所示
autocommit属性值等于ON,证明是自动提交是打开的
autocommit属性为on,自动提交是开启的状态,这样你每执行一条语句就会提交一次事务,如果我们想演示事务,可先暂时关闭
运行下列sql语句:
SET autocommit = off;
再次查询:
SHOW VARIABLES LIKE'%commit%';
执行语句:START TRANSACTION;
Mysql默认都是每执行一条DML(增删改)语句,会自动提交一次事务,但是当我们开启事务的时候,就会变成手动提交,如果我们后面没有执行提交commit操作,那么在此次事务之后执行的SQL只是在这个窗口的临时数据变化,并不是持久化到硬盘的变化,再打开另一个窗口的时候查询数据,数据是不生效的
无论执行什么sql,这里会被当做是事务中一个单一的逻辑
例如:UPDATE account SET balance = balance - 500 WHERE NAME = 'zhangsan';
UPDATE account SET balance = balance - 500 WHERE NAME = 'lisi';
这里就是两个单一的逻辑
执行sql:commit;或者 rollback;
关闭了自动提交之后事务的控制就掌握在你自己手里了,而不是原先的写一条语句执行,就自动提交完成了一次事务,现在是只要你每执行commit;或者 rollback;,这个事务就不会结束,数据就一直在内存中,不会进入硬盘中
所以当执行了上述操作后,不执行commit命令数据不会生效
Mysql是自动提交的,一条DML(增删改)语句会自动提交一次事务
Oracle 数据库默认是手动提交事务,需要执行commit,通常我们在客户端都有个按钮commit
查看事务的默认提交方式:SELECT @@autocommit; -- 1 代表自动提交 0 代表手动提交
修改事务提交的两种方式: set @@autocommit = 0;或者SET autocommit = off;(设置成手动提交)
查看事务提交方式:show variables like'%commit%';
> 指的是 事务中包含的逻辑,不可分割,是最小的操作单位。
例如,转账,A账户减少,B账户增加。虽然是两条 DML语句,但是被当做是一个整体,一次事务。两条语句只能同时成功或者同时失败
> 指的是 事务执行前后。数据完整性(总数不变)
(简单解释就是账户A和B,要么都是转账前的状态,要么都是转账后的状态。不能A账户的钱减少了但是B账户的钱没有增加,事务执行前和执行后,数据的总和相同,他们钱的总数是不会变的)
> 指的是 事务在执行期间不应该受到其他事务的影响
事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立
> 指的是 事务执行成功,那么数据应该持久保存到磁盘上。
事务提交成功后,数据修改永远生效
在考虑事务的隔离级别之前,需要认识到如果不考虑事务的隔离性,会发生的异常情况
总共分为两类:读和写的问题
简单解释:指的是一个事务读到另外一个事务还未提交的数据,读到的是数据库内存中的数据,而并非是真正磁盘上的数据
在同一个事务内,多次读同一个数据时,发现该数据已经被另一个已经提交的事务修改。(在一个事务内两次读到的数据时是不一样的。)
一个事务根据相同的查询条件,重新执行查询,返回的记录中包含与前一次执行查询返回的记录不同的行。(一个事务读到了另一个事务插入的数据 ,造成前后查询结果不一致 。)
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted (读未提交)、Read committed(读已提交) 、Repeatable read (重复读)、Serializable (可串行化),这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题
√: 可能出现 ×: 不会出现
脏读
不可重复读
幻读
Read uncommitted
√
√
√
Read committed
×
√
√
Repeatable read
×
×
√
Serializable
×
×
×
从上面的表格我们可以看出
读未提交
> 引发问题: 脏读 不可重复读 幻读
读已提交
> 解决: 脏读 , 引发: 不可重复读 幻读
可重复读
> 解决: 脏读 、 不可重复读 , 未解决: 幻读
可串行化
> 解决: 脏读、 不可重复读 、 幻读。
注意:我们讨论隔离级别的场景,主要是在多个事务并发的情况下,因此,接下来的讲解都围绕事务并发。
公司发工资了,领导把5000元打到singo的账号上,但是该事务并未提交,而singo正好去查看账户,发现工资已经到账,是5000元整,非常高兴。可是不幸的是,领导发现发给singo的工资金额不对,是2000元,于是迅速回滚了事务,修改金额后,将事务提交,最后singo实际的工资只有 2000元,singo空欢喜一场。
出现上述情况,即我们所说的脏读 ,两个并发的事务,“事务A:领导给singo发工资”、“事务B:singo查询工资账户”,事务B读取了事务A尚未提交的数据。
当隔离级别设置为Read uncommitted 时,就可能出现脏读,如何避免脏读,请看下一个隔离级别。
singo拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转账,把singo工资卡的2000元转到另一账户,并在 singo之前提交了事务,当singo扣款时,系统检查到singo的工资卡已经没有钱,扣款失败,singo十分纳闷,明明卡里有钱,为何出现上述情况,即我们所说的不可重复读 ,两个并发的事务,“事务A:singo消费”、“事务B:singo的老婆网上转账”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。当隔离级别设置为Read committed 时,避免了脏读,但是可能会造成不可重复读。
大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。如何解决不可重复读这一问题,请看下一个隔离级别。
当隔离级别设置为Repeatable read 时,可以避免不可重复读。当singo拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),singo的老婆就不可能对该记录进行修改,也就是singo的老婆不能在此时转账。
虽然Repeatable read避免了不可重复读,但还有可能出现幻读 。singo的老婆工作在银行部门,她时常通过银行内部系统查看singo的信用卡消费记录。有一天,她正在查询到singo当月信用卡的总消费金额为80元,而singo此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录,并提交了事务,随后singo的老婆将singo当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,singo的老婆很诧异,以为出现了幻觉,幻读就这样产生了。
注:Mysql的默认隔离级别就是Repeatable read。
Serializable 是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。如果有一个连接的隔离级别设置为了串行化 ,那么谁先打开了事务, 谁就有了先执行的权利,谁后打开事务,谁就只能等着,等前面的那个事务,提交或者回滚后,才能执行。但是这种隔离级别一般比较少用。容易造成性能上的问题。效率比较低。类似多线程变成了单线程,必须逐个执行
注意:隔离级别从小到大安全性越来越高,但是效率越来越低
select @@tx_isolation(默认级别是可重复读)
从默认的重复读到读未提交:set session transaction isolation level read uncommitted;
完整的SET TRANSACTION语句: SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
对上面的语句进行解释:
注意:默认的行为(不带session和global)是为下一个(未开始)事务设置隔离级别。如果你使用GLOBAL关键字,语句在全局对从那点开始创建的接下去所有新连接设置默认事务级别。使用SESSION 关键字为将来在当前连接上执行的事务设置默认事务级别,如果是SESSION则只要关闭当前连接,创建新连接他的隔离级别又会变回REPEATABLE READ可重复读。
此时再次查询当前连接的事务级别,就变成了读未提交