【MySQL】事务
事务
一条或多条sql语句组成一个执行单位,一组sql语句要么都执行要么都不执行
1、事务的四大特性:
原子性A
一致性C
隔离性I
持久性D
2、事务分类
3、 事务并发问题:
3.1 脏读(Dirty Read)
当一个「事务T1」正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另一个「事务T2」也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是「脏数据」,依据「脏数据」所做的操作可能是不正确的。
脏数据一般是指不符合要求以及不能直接进行相应分析的数据。
脏数据包括:缺失值、异常值、不一致的值、重复数据及含有特殊符号(如#、¥、*)的数据。
T1 被 T2 影响到了,但是实际上余额是 100,T1 中使用了 150 去做计算的话,会造成一系列的问题。
时间点 | 事务T1 | 事务T2 |
---|---|---|
1 | 开启事务T1 | |
2 | 开启事务T2 | |
3 | 查询余额 100 | |
4 | 修改余额 150 | |
5 | 查询余额 150 | |
6 | 事务回滚 |
3.2 不可重复读(Unrepeatable Read)
指在一个「事务T1」内多次读同一数据。在一个「事务T1」还没有结束时,另一个「事务T2」也访问该数据。那么,在「事务T1」中的两次读数据之间,由于「事务T2」的修改导致「事务T1」两次读取的数据可能不太一样。
这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
时间点 | 事务T1 | 事务T2 |
---|---|---|
1 | 开启事务T1 | |
2 | 开启事务T2 | |
3 | 查询余额 100 | |
4 | 修改余额 150 | |
5 | 查询余额 100 | |
6 | 提交事务 | |
7 | 查询余额 150 |
3.3 幻读(Phantom Read)
幻读与不可重复读类似。它发生在一个「事务T1」读取了几行数据,接着另一个并发「事务T2」插入了一些数据时。在随后的查询中,第一个「事务T1」就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
时间点 | 事务T1 | 事务T2 |
---|---|---|
1 | 开启事务T1 | |
2 | 开启事务T2 | |
3 | 查询 Id>3,共 3 条数据 | |
4 | 新增一条 Id=9 | |
5 | 提交事务 | |
6 | 查询 Id>3,共 4 条数据 |
3.4 不可重复度和幻读区别:
不可重复读的重点是修改,导致两次数据不一致;幻读的重点在于新增或者删除,导致两次结果集不一致。
解决不可重复读的问题只需锁住满足条件的行;解决幻读需要锁表。
例 1(同样的条件,你读取过的数据,再次读取出来发现值不一样了 ):事务 1 中的 A 先生读取自己的工资为 1000 的操作还没完成,事务 2 中的 B 先生就修改了 A 的工资为2000,导致 A 再读自己的工资时工资变为 2000;这就是不可重复读。
例 2(同样的条件,第 1 次和第 2 次读出来的记录数不一样 ):假某工资单表中工资大于 3000 的有 4 人,事务 1 读取了所有工资大于 3000 的人,共查到 4 条记录,这时事务 2 又插入了一条工资大于 3000 的记录,事务 1 再次读取时查到的记录就变为了 5 条,这样就导致了幻读。
4、事务的隔离级别:
事务隔离级别,就是为了解决上面几种问题而诞生的,用于决定如何控制并发用户读写数据的操作。
- 读未提交(Read Uncommitted)
- 读已提交(Read Committed)
- 可重复读(Repeatable Read)
- 串行化(Serializable)
事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。所以设立了几种事务隔离级别,以便让不同的项目可以根据自己项目的并发情况选择合适的事务隔离级别。
可以使用 select @@tx_isolation 来查看默认隔离级别
4.1 读未提交(Read Uncommitted)
读未提交:能够读到没有提交的数据,因此这个隔离级别,无法解决脏读、幻读和不可重复读。
4.2 读已提交(Read Committed)RC
读已提交:即能够读到那些已经提交的数据,因此能够防止脏读,但是无法限制不可重复读和幻读。
4.3 可重复读(Repeatable Read)RR
可重复读:指的是在数据读出来之后加锁,这个事务不结束,别的事务就不可以修改这条记录。这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
4.4 串行化(Serializable)
串行化:是最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务。这样就解决了脏读、不可重复读和幻读的问题了,但是性能的消耗也是最大的。
4.5 隔离级别出现并发问题的可能性
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
---|---|---|---|---|
Read Uncommitted | 是 | 是 | 是 | 否 |
Read Committed | 否 | 是 | 是 | 否 |
Repeatable Read | 否 | 否 | 是 | 否 |
Serializable | 否 | 否 | 否 | 是 |
注意:不是事务隔离级别设置得越高越好,事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,那么效率就要降低,因此实际开发中往往要在效率和并发正确性之间做一个取舍。
一般情况下会设置为 READ_COMMITED,此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了。
MySQL 数据库默认的隔离级别为 REPEATABLE_READ。(属于历史原因)
4.6 查询及设置事务隔离级别
# 查看默认的事务隔离级别 MySQL默认的是 repeatable read
select @@tx_isolation
# 设置事务的隔离级别
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;
4.7 数据库编写常见的优化措施:
1、对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
3、应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。
4、应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描;in 和 not in 也要慎用,否则会导致全表扫描;like 模糊全匹配也将导致全表扫描。