目录
很荣幸与诸君在篇文章“相遇”,祝大家身体健康,感谢大家的莅临指导!
本文分享、讨论的重点在事务的理论部分,如何开启MySQL中事务的操作也会提及,但不是重点。本文仅仅是事务的初阶知识,还有一些进阶的内容会在我接下里的MySQL进阶篇提及。
一、事务的引入⭐⭐⭐⭐⭐
1. 为什么需要事务
在日常开发中,很多操作,不是通过一个SQL就能完成,往往需要多个SQL配合完成。当执行多个SQL操作的时候,如果中间出现了特殊的情况(如程序崩溃、系统崩溃、网络断开,主机断电了……)可能就会出现,前面的SQL执行成功,后面的SQL执行失败了。
考虑场景,如:转账
当客户执行转账操作时,涉及从一个账户扣除资金并将其存入另一个账户。这个过程可能包括多个数据库操作,如查询余额、扣款和存款等。使用事务可以确保转账操作的原子性,即要么全部成功完成,要么完全不执行,从而避免出现资金丢失或不一致的情况。
重点:这时候就需要引入事务来保证多个SQL语句要么都执行,要么都不执行!
事务,把多个操作打包成一个整体,就能够保证这个整体要么都执行成功,要么就“一个都不执行”。有效避免了部分执行,部分未执行产生一些“中间状态引起”的问题。
上面说道的要么一个都不执行 并非 是真的没有执行
事务中的若干个SQL必然是要一条一条地执行的 ~,但是事务能够保证当执行到某一条SQL语句如果出现了问题,数据库就能自动地把前面SQL造成的影响给恢复回来,恢复如初,看起来就好像一条SQL都没有执行过的样子。
重点:数据库事务的原子性,核心就是通过“回滚”机制来保证!
既然上面说到了事务的四大特性中的一个——原子性,那我们就先来讨论一下事务的四大特性。
2. 事务的四大特性
事务具有ACID四大特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(lsolation)和持久性(Durability):
(1)原子性(Atomicity):原子性指事务是一个不可分割的工作单位,要么全部执行成功,要么全部执行失败回滚。在事务中的操作要么全部提交,要么全部回滚,不存在部分提交的情况。这确保了数据的完整性,即数据库在任何时候都处于一致的状态。
(2)一致性(Consistency):一致性指事务使数据库从一个一致性状态转变到另一个一致状态,不会出现数据对不上的情况。在事务执行前后,数据应该保持一致性。如果事务执行成功,数据应该处于一致状态;如果执行失败,则数据库应该恢复到事务开始前的状态。一致性规则由业务逻辑来定义。
(3)隔离性(Isolation)⭐⭐⭐⭐⭐:隔离性指多个事务并发执行时,每个事务的操作应该与其他事务隔离开来,互不干扰。事务之间应该是相互独立的,避免数据相互影响。隔离级别可以控制事务之间的可见性和影响范围,常见的隔离级别包括读未提交、读已提交、可重复读和串行化。
具体描述如:数据库并发执行事务(多个客户端同时给服务器发起事务),每个客户端啥时候把事务提交过来?服务器不知道,很可能多个客户端正好就把事务赶到一块了,就需要数据库服务器都能给出处理,更糟糕的是,如果这多个事务都尝试操作同一个表,情况会变得更糟!就类似于“一心多用”,这时候就需要数据库服务器把这多个事务都能处理好。
重点:在下面还会进一步讨论事务的隔离性及其事务在并发会遇到的三种常见问题!
(4)持久性(Durability):持久性指一旦事务提交,其对数据库的修改应该是永久性的,即使系统发生故障也不会丢失。系统必须能够保证事务提交后对数据的修改是持久的,并能够在系统故障后恢复数据。持久性通常通过日志记录和数据备份来实现 。
总结:这四大特性确保了事务的正确性、可靠性和稳定性,是数据库管理系统保证数据完整性和一致性的重要基础。当事务满足ACID四大特性时,可以确保数据库在各种异常情况下依然能够保持数据的正确性和稳定性。
二、事务的具体细节⭐⭐⭐⭐⭐
1. 事务在并发会遇到的三种常见问题
(1)脏读(Dirty Read):
- 产生原因:脏读指一个事务读取了另一个事务未提交的数据。当一个事务修改了数据但尚未提交,另一个事务读取了这个未提交的数据,就会导致脏读。
- 处理方式:避免脏读的方法是使用适当的隔离级别,如读已提交(Read Commited) 或串行化(Serializable) 隔离界别。读已提交隔离级别可确保一个事务只能读取到已提交的事务,从而避免的脏读。
举个例子:
有位老师在备课,在计算机上敲了一段代码,这时有位同学路过的时候,偷偷瞄了一下老师的屏幕,看到屏幕上的一段代码,于是暗暗记下来了。当这位同学看完走后,老师把这段代码一改,最终在老师上课讲的代码和同学瞄到的代码可能不一样了。诶,这就像极了脏读,那么如何解决呢?
解决方案:
老师和同学们作了个约定,大家以后不要再瞄我的屏幕了。如果想提前预习课程,提前看到代码,可以去老师的码云上看。诶,这个解决方案是不是就是类似于隔离级别读已提交,同学们只能去读老师已经提交到码云上的代码。
这是对“读”或“写”上锁呢?各位小伙伴们想一想。
答:上述约定,就相当于是针对“写操作”加锁,我这边写的时候(啪一下~,把房门一关,诶,同学们就没办法偷偷瞄老师屏幕上的代码,读到老师提交的代码),你不能读,只有当我写完,你才能读。
现在引入了写加锁之后,执行写操作事务A的过程中,读操作事务B就不能执行了,要等待。这就相当于降低了“并发能力”,也就会降低数据库服务器的处理效率,提高了“隔离性”,也提高了数据的准确性。
(2)不可重复读(Non-Repeatable Read):
- 产生原因:不可重复读指一个事务在多次查询同一个数据时,由于其他事务的修改导致数据不一致。例如:一个事务在两次读取同一数据之间,另一个事务修改了该数据,导致了一个事务两次读取的数据不一致。
- 处理方式:可重复读隔离级别可以避免不可重复读问题。在可重复读隔离级别下,一个事务在多次读取同一数据时,其他事务对该数据的修改不会影响到第一个事务的结果。
举个例子:
顺成了上一个栗子的情况,老师已经和同学们约定好了,同学们只能来读取老师提交到码云的代码。现在,老师写了一些代码(事务A),把代码提交到码云了,有同学发现老师的码云更新了(1min之前),同学们赶紧来读代码(事务B)。诶,这个时候老师突然想到之前提交的代码有问题,还得修改修改,赶紧改了代码后,重新提交了(事务C)。
其中的事务B:同学读的过程中,本来看到的都是事务A中的结果,读着读着,代码突然就变了!!!(因为老师重新提交了代码)
诶,上述的栗子就是在说明同学们无法重复读老师重新提交的数据,称为“不可重复读问题”。
存在三个事务 ABC,事务A针对数据进行的修改、提交;接下来事务B进行读取数据(事务B这里的多个SQL都要进行读操作),在执行B的过程中,又有一个事务C针对数据进行修改,就会使事务B中的不同读操作,读出来的结果不一样。
“读已提交”是对“写操作”进行上锁了,而没有约定读的时候“不能写”。那么如何解决“不可重复读问题”呢?再进一步约定,给读操作也加锁!
- 写操作加锁:我写的时候,别人不能读;
- 读操作加锁:别人读的时候,我不能写;
对照我们上面的栗子:就是和同学们进一步约定,老师提交代码之后,就不要改了,同学们读完之前,这个时候老师都不能修改了。
此时引入了读加锁,就会使得“并发程度”又进一步降低。效率也会随之降低“隔离性”又进一步提高,数据的准确性也会提高,这个时候事务A、B、C就不能笔法执行了。
(3)幻读(Phantom Read):
- 产生原因:幻读指一个事务在两次查询之间,另一个事务插入了新的数据,导致第一个事务第二次查询看到了不一致的数据。这种情况通常发生在范围查询操作中。
- 处理方式:使用更高级别的隔离级别,如可重复读(Repeatable Read) 或串行化隔离可以避免幻读问题。这些隔离级别可以确保一个事务在执行期间可以多次读取相同的数据,从而避免出现幻读的现象。
举个例子:
刚刚约定了,针对“读操作”和“写操作”都加锁了。
比如,老师写了代码,提交了,同学们开始读代码,按约定来说,此时老师不能修改代码。
但是老师这时闲着没事,虽然不修改代码,但是老师创建了一个新的类/文件,针对新的类/文件进行修改,并提交~。这样做,虽然同学们读到的代码是一样的,但是发现多出来新的文件。
事务A先修改并提交数据,事务B进行读数据,此时事务C没有修改事务B读的数据,但是给对应的表进行了新增数据 / 删除数据等操作……,导致事务B中,读到的数据级不同。
解决方案:
“串行化”使得所有的事务都严格按照“一个接一个”的方式执行,完全没有并发了,此时执行效率是最低的,隔离性也是最高的,数据也是最准确的。
总结:上述三个问题,就是在并发执行事务过程中,可能会产生的三个典型问题。
- 脏读:事务B读到了事务A中未提交的临时数据(脏数据)=> 写加锁
- 不可重复读:事务B读的过程中,又有一个事务C对刚才的事务A提交的数据进行了修改,使事务B内部不同的读操作读到的结果不同 => 读加锁
- 幻读:和不可重复读类似,事务B读的过程中,事务C没有修改数据内容,而是修改了“结果集”,导致B内部不同的读操作读到的结果集合不同。
解决上述的问题过程中,要想让数据更准确,就需要牺牲一部分并发/效率,很难做到又高效又准确。只能做“权衡”,看你当前业务常见,是更关注效率,还是更关注准确性。
重点:这里最难区分就是“不可重复读”和“幻读”,我和诸位进一步讨论下~
幻读(Phantom Read)和不可重复读(Non-Repeatable Read)是数据库并发控制中两种不同的现象,它们的区别如下:
-
幻读(Phantom Read):
- 定义:幻读指在一个事务内部,由于其他事务插入或删除数据,导致同一查询在不同时间点返回不同数量的数据行的现象。
- 产生原因:幻读通常发生在范围查询操作中,当一个事务在读取某个范围的数据时,另一个事务在该范围内插入了新的数据,导致第一个事务多次查询时看到了不同数量的数据行。
- 解决方法:避免幻读可以通过使用更高级别的隔离级别,如可重复读(Repeatable Read)或串行化(Serializable)隔离级别。
-
不可重复读(Non-Repeatable Read):
- 定义:不可重复读指一个事务在多次读取同一数据时,由于其他事务的修改导致数据不一致的现象。也可以理解为一个事务在两次读取同一数据之间,另一个事务修改了该数据。
- 产生原因:不可重复读通常发生在一个事务进行读取操作时,另一个事务对相同数据进行了修改操作,导致第一个事务两次读取的数据不一致。
- 解决方法:可重复读(Repeatable Read)隔离级别可以避免不可重复读问题。在可重复读隔离级别下,一个事务在多次读取同一数据时,其他事务对该数据的修改不会影响到第一个事务的结果。
总的来说,幻读强调的是在一个范围查询中出现新增或删除数据的情况,导致查询结果不一致;而不可重复读强调的是同一事务多次读取同一数据时,由于其他事务的修改导致数据读取结果不一致的情况。在处理这两种并发问题时,需要根据具体场景选择合适的隔离级别来确保数据的一致性和正确性。
2. MySQL事务隔离的四种级别
(1)read uncommitted:允许读取其他事务未提交的数据 => 啥事没干,没有解决脏读 + 不可重复读 + 幻读;并发程度最高,隔离性最差。
(2)read committed:只能读取其他事务提交后的数据 => 解决了脏读,存在不可重复读和幻读,并发程度低,隔离性提高了。
(3)repeatable read:针对读操作和写操作都加锁了 => 解决了脏读 + 不可重复读,存在幻读;并发程度又降低,隔离性又提高了。
(4)串行化(serializable):所有事务都是串行执行的 => 解决了脏读 + 不可重复读 + 幻读;并发基本没有,隔离性最高。
三、MySQL中如何开启事务
(1)开启事务:start transaction;
(2)执行多条SQL语句;
(3)回滚或提交:rollback/commit;
说明:rollback即是全部失败,commit即是全部成功。
start transaction;
-- 阿里巴巴账户减少2000
update accout set money=money-2000 where name = '阿里巴巴';
-- 四十大盗账户增加2000
update accout set money=money+2000 where name = '四十大盗';
commit;
四、补充
关于MySQL的索引和事务的细节不单单只有这些,他们一些底层逻辑也是我未来的命题之一,一些比较难、比较细节的知识点,我会放到MySQL进阶篇与诸位分享,大家一起讨论。
愿与诸君共勉,加油!