目录
刚开始我们拿一个常见的操作来说明我们为什么需要事务.假如现在张三给李四转账100元,我们需要先把张三账户的钱减少100,然后再把李四账户的钱增加100.但是如果张三账户的钱减少100的时候突然发生网络异常,那么张三减少了100,但是李四却没有增加100,这个时候就会出现问题了.
update accout set money=money-100 where name='张三'
update accout set money=money+100 where name='李四'
为了避免这种问题的发生,因此出现了事务(transaction)
原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency) 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
隔离性(Isolation) 事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。(具体见事务的隔离级别)
持久性(Durability) 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
我们可以通过start transaction来开启事务 通过rollback来回滚数据,通过commit来提交数据
案例:对于这样一张accout表
1.发生错误rollback数据的情况;
start transaction;
update accout set money=money-100 where name='张三'
#发生网络错误
rollback;
表中数据未发生改变
2.未发生错误commit数据
start transaction;
update accout set money=money-100 where name='张三';
update accout set money=money+100 where name='李四';
commit;
表中数据发生改变:
之前我们都进行过修改,添加和删除操作,这些语句都直接修改了表中的数据.那么大胆推测以下,每一条语句都是一个事务,执行完这一条语句都自动提交到了数据库中.
事实就是如此,我们可以通过以下语句来验证我们的推测.
show variables like '%commit%';
我们可以看到autocommit=on.
我们可以通过以下语句来设置不自动提交(不推荐)
set autocommit = 0; #0-->OFF 1-->ON
MySQL是以服务的形式发布到网络上的,如果有多个用户对一个表进行操作,那么他们之前的操作可能会发生相互影响的情况.
隔离级别:隔离级别是数据库中用于控制并发访问时数据一致性的一种机制。当多个事务同时访问数据库时,会出现一些并发问题,如脏读、不可重复读和幻读。隔离级别定义了一个事务对于另一个事务所做的修改的可见性和影响范围,从而保证数据的一致性和正确性。
这张表具象的描述了四种隔离级别的描述
这张表表明了四种隔离级别可能出现的问题和解决的问题
这四种隔离级别从上到下的安全性越来越好,但是性能越来越差
脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
查看MySQL中默认的隔离级别
select @@tx_isolation;
#或者
show variables like 'tx_isolation';
修改默认的隔离级别
set @@global.tx_isolation = '隔离级别' ; // 全局set @@session.tx_isolation = '隔离级别' ; // 当前 sessionset @@tx_isolation = '隔离级别' ;// 仅对下⼀个事务生效
案例:修改隔离级别为READ-UNCOMMITTED
set @@global.tx_isolation = 'READ-UNCOMMITTED' ; // 全局set @@session.tx_isolation = 'READ-UNCOMMITTED' ; // 当前 sessionset @@tx_isolation = 'READ-UNCOMMITTED' ;// 仅对下⼀个事务生效
注意:设置了全局的隔离级别之后,需要重启客户端之后才能生效
先来建立一个user表
create table user(
id int,
name varchar(20)
);
这种情况下用户读到了没有被commit的数据,接来下我们测试read-uncommited隔离级别下可能发生的情况.
我们先把MySQL的隔离级别设置为READ-UNCOMMITED
set @@global.tx_isolation = 'READ-UNCOMMITTED';
#重启客户端之后再来查询隔离级别,否则不生效
show variables like 'tx_isolation';
显示这样表示修改成功.
接下来我们打开两个客户端(终端)
初始状态客户端一和客户端二都只含有四条数据.
我们在客户端一开启事务,并且向表中添加一条数据.
START TRANSACTION;
INSERT INTO USER(id,NAME) VALUES(5,'czj');
SELECT * FROM USER;
此时客户端表中的数据为左图所示
我们在客户端二查询数据,发现数据和表一种的数据一模一样.
此时我们在客户端一rollback操作,查询数据
此时数据为这样
然后我们在客户端二在进行查询操作
数据也变成了这样
客户端二读取到了客户端一还没有commit的数据,这种情况称之为脏读,因为客户端一可能进行rollback操作,所以客户端二读到的没有commit的数据是无效的,因此这种情况使我们一定要避免产生的.
流程图:
案例:这就相当于作者在CSDN中发表了一篇文章,读者读取到了这一篇文章,但是这个时候作者对这篇文章进行了修改(还没有提交),读者刷新一下,发现了修改的内容,但是作者发现修改不好,就放弃了修改(相当于rollback),读者又刷新了一下,发现修改的内容又恢复了原样.
针对READ-UNCOMMITED出现的问题,READ-COMMITED这种隔离级别可以很好的解决.
首先我们将数据库的隔离级别设置为READ-COMMITTED
set @@global.tx_isolation = 'READ-COMMITTED';
#重启客户端之后再来查询隔离级别,否则不生效
show variables like 'tx_isolation';
当出现这种情况的时候表示隔离级别设置成功
我们同样执行上面的操作,发现没有commit之前客户端二的数据都是这样就成功的解决了脏读的问题.
READ-COMMITED到底是如何解决脏读的问题呢,其实在这种隔离级别下,我们在事务中加入了一把读锁,这个时候从其他的客户端是无法读取到这个客户端事务中的内容的,只能读取到事务开启之前的内容,当rollback/commit操作的时候,这个时候读锁解除.
但是这种隔离级别下也会出现问题,下面我们来模拟
我们在客户端一中开启事务查询用户信息
START TRANSACTION;
SELECT * FROM USER;
查询到的数据如图
这个时候我们在客户端二也开启一个事务,修改数据,并且将commit操作
START TRANSACTION;
UPDATE USER SET NAME='xcl' WHERE id=4;
COMMIT;
SELECT * FROM USER;
客户端二的数据如图.
此时我们在客户端一(事务还没有结束)查询user的信息
SELECT * FROM USER;
客户端一的数据如图
以上所描述的类型就是不可重复读
以上操作的流程图
但是事务的原子性的特性,一个事务中的数据是一个整体,怎么可能一个事务中查询到数据不同呢.
案例:这就相当于作者在CSDN中发表了一篇文章,读者读取到了这一篇文章,但是这个时候作者对这篇文章进行了修改,并且进行了提交,读者刷新一下,就发现了修改的内容.
针对READ-COMMITED出现的问题,REPEATABLE-READ这种隔离级别可以很好的解决.
首先我们将数据库的隔离级别设置为REPEATABLE-READ
SET @@global.tx_isolation = 'REPEATABLE-READ';
#重启客户端之后再来查询隔离级别,否则不生效
SHOW VARIABLES LIKE 'tx_isolation';
出现这种情况表示设置成功
我们同样执行以上的操作,发现
在客户端一的事务没有结束之前,查询到的数据都为左表所示
REPEATABLE-READ这种隔离级别通过给事务加上读锁,这样客户端一种查询只能查询到开启事务时的数据(也就是加锁的时候),之后改变的数据无法进入到读锁开启到结束的这段时间(也就是这段事务的期间),因此这期间内读到的数据都是一样的,这样成功解决了不可重复读的问题.
START TRANSACTION;
SELECT * FROM USER;
客户端一显示这些信息
客户端二:
SELECT * FROM USER;
START TRANSACTION;
INSERT INTO USER(id,NAME) VALUE(5,'xcl');
COMMIT;
SELECT * FROM USER;
客户端二显示这些信息
然后再从客户端一查询信息(此时客户端一的事务还没有结束)
客户端一显示这些信息
当客户端一执行commit操作(事务结束),然后查询
没有出现幻读现象,说明REPEATABLE-READ已经解决了一部分的幻读问题,MySQL默认的隔离等级为REPEATABLE-READ已经很合理了.
对于update还是没有解决,此时采用update操作
之前采用select进行查询的时候,是不会出现新增加的数据的
update user set name='zyf' where id=5;
然后再次执行select操作,此时新增加的数据就能被查询到
针对REPEATABLE-READ出现的问题,SERIALIZABLE这种隔离级别可以很好的解决.
我们将数据库的隔离级别设置为SERIALIZABLE
SET @@global.tx_isolation = 'SERIALIZABLE';
#重启客户端之后再来查询隔离级别,否则不生效
SHOW VARIABLES LIKE 'tx_isolation';
这样可以彻底解决幻读问题.