定义
- 事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务。
- 一个完整的业务需要批量的DML(insert,update,delete)语句共同联合完成。
- 事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同
事务的四大特征
- 原子性(A):事务是最小的单位,不可再分。
- 一致性(C):事务要求所有的DML语句操作的时候,要么同时成功,要么同时失败。及如果在同一事物中,有一步骤报错,整个事务即将回滚到该事务操作之前的状态。
- 隔离性(I):事务A和事务B之间具有隔离性。及事务A在同数据a进行操作时,只有事务A结束,事务B才可以对数据a进行操作。
- 持久性(D):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
对事务的操作术语
- 开启事务:Start Transaction
- 事务结束:End Transaction
- 提交事务:Commit Transaction
- 回滚事务:Rollback Transaction
事务与数据库底层数据
在事物进行过程中,未结束之前,DML语句是不会更改底层数据,只是将历史操作记录一下,在内存中完成记录。只有在事物结束的时候,而且是成功的结束的时候,才会修改底层硬盘文件中的数据
设置事务的隔离级别
一,修改my.ini文件
使用transaction-isolation选项来设置服务器的缺省事务隔离级别。
– READ-UNCOMMITTED
– READ-COMMITTED
– REPEATABLE-READ
– SERIALIZABLE
• 例如:
[mysqld]
transaction-isolation = READ-COMMITTED
二,通过命令动态设置隔离级别
其语法模式为:
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>
其中的<isolation-level>可以是:
– READ UNCOMMITTED
– READ COMMITTED
– REPEATABLE READ
– SERIALIZABLE
• 例如: SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
隔离级别的作用范围
• 事务隔离级别的作用范围分为两种:
– 全局级:对所有的会话有效
– 会话级:只对当前的会话有效
• 例如,设置会话级隔离级别为READ COMMITTED :
mysql> SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
或:
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
• 设置全局级隔离级别为READ COMMITTED :
mysql> SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
查看隔离级别
- 查看当前会话的隔离界别
- select @@tx_isolation
- 查看系统的隔离界别
- select @@global.tx_isolation
并发问题
脏读
事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
不可重复读
事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
幻读
系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
四大隔离级别
- 读未提交:read uncommitted
事物A和事物B,事物A未提交的数据,事物B可以读取到这里读取到的数据叫做“脏数据”。这种隔离级别最低,
这种级别一般是在理论上存在,数据库隔离级别一般都高于该级别。
- 读已提交:read committed
- 事物A和事物B,事物A提交的数据,事物B才能读取到
- 这种隔离级别高于读未提交
- 换句话说,对方事物提交之后的数据,我当前事物才能读取到
- 这种级别可以避免“脏数据”
- 这种隔离级别会导致“不可重复读取”
- Oracle默认隔离级别
- 可重复读:repeatable read
- 事务A和事务B,事务A提交之后的数据,事务B读取不到
- 事务B是可重复读取数据
- 这种隔离级别高于读已提交
- 换句话说,对方提交之后的数据,我还是读取不到
- 这种隔离级别可以避免“不可重复读取”,达到可重复读取
- 比如1点和2点读到数据是同一个
- MySQL默认级别
- 虽然可以达到可重复读取,但是会导致“幻像读”
- 串行化:serializable
- 事务A和事务B,事务A提交之后的数据,事务B读取不到
- 事务B是可重复读取数据
- 这种隔离级别高于读已提交
- 换句话说,对方提交之后的数据,我还是读取不到
- 这种隔离级别可以避免“不可重复读取”,达到可重复读取
- 比如1点和2点读到数据是同一个
- MySQL默认级别
- 虽然可以达到可重复读取,但是会导致“幻像读”
分析四种隔离级别前的准备
创建银行账户表
create table account(
id int primary key,
name varchar(50),
balance double);
向account表中出入数据
insert into account values (1,'lilei',450),(2,'hanmei',16000),(3,'lucy',2400);
打开两个CMD窗口,模拟两个客户端一个设置为A一个设置为B
- 1,读未提交
在客户端A的操作
在客户端B的操作,此时A的事务未提交。
完成上图的操作,但是客户端B并未提交事务,现在在客户端A查询数据。
客户端A查询到了客户端更新的未提交事务的数据。如果此时客户端B回滚事务。则客户端A读取到的数据就是脏数据。
在客户端A执行更新语句update account set balance = balance - 50 where id =1
,lilei的balance没有变成350,居然是400,是不是很奇怪,数据不一致啊,如果你这么想就太天真 了,在应用程序中,我们会用400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别
- 2,读已提交
打开一个客户端A,并设置当前事务模式为read committed(未提交读),查询表account的所有记录:
在客户端A的事务提交之前,打开另一个客户端B,更新表account:
这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:
客户端B的事务提交
客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题
- 3,可重复读
打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的所有记录
在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交
在客户端A查询表account的所有记录,与步骤(1)查询结果一致,没有出现不可重复读的问题
在客户端A,接着执行update account set balance = balance - 50 where id = 1
,balance没有变成400-50=350,lilei的balance值用的是步骤(2)中的350来算的,所以是300,数据的一致性倒是没有被破坏。可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)。
重新打开客户端B,插入一条新数据后提交
在客户端A查询表account的所有记录,没有 查出 新增数据,所以没有出现幻读
- 4,串行化
打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:
打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。