何为事务?
事务(Transaction)是指一组操作的集合,这些操作要么全部执行成功,要么全部不执行。事务通常用于保证数据库的一致性、完整性和可靠性,确保数据的完整性与正确性。
有效避免部分执行,部分未执行而引起的问题;
引入例子:
假设以下场景: 转账
第一步A用户转账给B用户100元 账户-100
sql-- 从账户A扣款 UPDATE accounts SET balance = balance - 100 WHERE account_id = 'A';
第二步B用户收到A转账100元 账户+100
sql-- 向账户B存款 UPDATE accounts SET balance = balance + 100 WHERE account_id = 'B';
此时如果第一步完成之后,由于受到各种原因,例网络波动,主机关机,MySQL服务器崩溃等等因素,导致第二步未操作,这可损失大了,A凭空丢失100,所以这种情况是不允许发生的。
此时由于是俩个操作,所有MySQL会将这俩个操作打包成一个操作(原子性),要么一起完成,要么都不完成。
如果中途发生异常,此时操作将会进行"回滚"操作,所有操作将会被撤销。
补充:MySQL会将每一步操作都会记录在MySQL的日志中,所以可以根据日志里面的内容进行还原操作
在 MySQL 中,事务是由多个 SQL 语句组成的一个操作单元,要么所有的操作都成功执行(提交,commit),要么在发生错误时,所有操作都不执行(回滚,rollback)。通过事务,MySQL 能够提供更强的错误恢复能力,并保证数据库在并发访问时的一致性。
事务的四大特性:
事务有四个核心特性,通常被称为 ACID 特性:原子性(Atomicity),一致性(Consistency),持久性(Durability),隔离性(Isolation);
原子性:
在上述操作中,将多个操作打包成一个操作时,此时代表这个操作具有"原子"的,原子就是以前最小的单位,不可拆分。
事务中的操作要么全部成功,要么全部失败。原子性确保了事务是最小的执行单位,无法再分割。即使在事务执行的过程中发生故障,数据库也会通过回滚操作使事务的状态恢复到事务开始之前。
一致性:
在执行事务开始前后,要保证数据是一致的,不会出现对不上的情况。一旦触发"回滚"操作,也要保证回滚回去的数据跟原来一样;
事务执行前后,数据库的状态必须从一个一致的状态转变到另一个一致的状态。所有事务都必须遵循数据库的完整性约束,如主键、外键等,确保数据的完整性和准确性。
持久性:
一旦事务提交,它对数据库的更改是永久性的,即使发生系统崩溃或断电,提交的事务所做的更改也会保留下来。
隔离性:
在并发环境下,多个客户端同时对服务器发起事务请求,一个事务的执行不应受到其他事务的干扰。每个事务应该像是在独占数据库一样执行,直到它提交或回滚。
在并发环境下,可能会产生以下问题:脏读,不可重复读,幻读;
脏读:
脏读指的是一个事务读取了另一个事务尚未提交的数据。这可能会导致读取到不一致的数据,因为另一个事务可能回滚,使得当前事务读取的数据变得无效。
例子:
假设有一场随堂考试,小明(学渣)抄袭小强(学霸)的试卷(这种行为是不好的,只是比喻),此时小强写完了试卷给小明抄,小明写着写着写完了之后,小明就直接提交试卷了。但小强在检查试卷的时候,发现检查试卷的时候很多答案填错了,那么小强肯定将答案改正了回来 ,而小明吃了大亏,心里想我*****,还不如自己写...................核心在于:当小强不仅写完试卷,并且还检查完毕,确保不会修改的情况下,小明再去抄写他的答案,不然都可能出错
解法方法:
当事务B查看事务A(写操作)的数据时候,将事务A的数据进行"加锁",此时事务B不能查看A的数据,必须等待事务A确定下来,才可以查看。
这就相当于降低了 '并发能力',也就降低了数据库服务器的处理效率,提高了 '隔离性' ,也提高了数据的准确性。在并发执行事务过程中,相互之间是互相影响的,彼此的影响越小,隔离性越高;反之影响越大,隔离性越低。
不可重复读
在上述基础上,我们已经把写操作加上锁,这就意味着读操作的事务不能中途查询,以免没有查询到最终结果。
例子:
当我们写博客的时候,此时读者必须等我们发布完成之后,才能阅读我们的博客。假设我们写完已经发布了 这是事务A
然后读者去查看博客的内容 这是事务B
当我们发布完 我们还觉得内容需要修改一下 可能存在一点问题 又进行发布了 这是事务C
问题出现在事务B此时在读数据的时候,此时我们事务C也执行了,假设博客页面刷新了,事务B发现此时读到的数据跟刚才读到的数据不一样,这就是不可重复读问题;
解法:
在事务B读操作的时候 ,我们也不能执行事务C,必须等事务B执行完之后,才能修改数据,也就是说在"读数据"的时候也不能"写数据";
所以对事务B读操作也加上"锁", 就可以保证事务 B 前后两次读取的数据都是一致的。
脏读和不可重复读的区别:
- 脏读关注的是一个事务读取另一个事务未提交的数据,且这个数据可能最终会被回滚,因此它是不可靠的。
- 不可重复读关注的是同一个事务内,在多次读取同一数据时,数据发生了变化(通常是由于其他事务的提交),导致读取结果不一致。
幻读:
幻读是指一个事务在查询数据时,其他事务可能插入、删除或修改记录,导致该事务查询的数据集发生变化,即查询结果出现"幻象"。在一个事务中,查询的结果集在不同的查询操作之间可能不一致。
例子:
刚才约定对读和写操作进行加上锁。
事务A发布第一篇博客
事务B在查询 博客的内容
此时我们并不修改第一篇博客的内容,闲着也是闲着,于是我们继续写了新的一篇博客,第二篇博客。 事务C
在B再一次查询的时候,发现了多出来了一篇博客,此时就是幻读。
解法:
当读者在阅读博客的时候,叫我们啥事别做了,好好休息,简单来说,就是一个事务一个事务来,在执行事务的时候,不要执行任何操作;
实现串行化操作,此时没有并发了,因此效率最低,并且也是隔离性最高的,数据是最准确的。
隔离性的隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 | 适用场景 |
---|---|---|---|---|
读未提交(Read Uncommitted) | 是 | 是 | 是 | 高并发、无需完全一致性的数据场景 |
读已提交(Read Committed) | 否 | 是 | 是 | 适用于实时性要求较高的场景 |
可重复读(Repeatable Read) | 否 | 否 | 是 | 适用于大部分应用,默认隔离级别 |
串行化(Serializable) | 否 | 否 | 否 | 保证最大程度的一致性,但性能最低 |
总结
- 脏读 、不可重复读 和幻读 都是由于并发事务互相干扰所导致的问题。为了避免这些问题,可以选择合适的事务隔离级别:
- 读未提交:出现脏读,不可重复读,幻读。
- 读已提交:防止脏读,但可能会出现不可重复读和幻读。
- 可重复读:防止脏读和不可重复读,但可能会出现幻读。
- 串行化:完全避免所有并发问题,但性能最差。