假设你想给张三转账500块钱,这时需要扣除你卡上的账户余额,并同时给张三卡上增加500块钱。如果转账的两个操作中的一个失败,那你就可能损失金钱或者让金钱消失不见,张三也就收不到钱了。
这时,事务就派上用场了。它可以保证这两个操作要么同时成功,要么同时失败,绝不会出现一半成功一半失败的尴尬局面。
所以,数据库为什么要有事务?其实就是为了保证业务正常运转,数据最终一致。
接下来我们聊一聊事务的 4 个特性:原子性、一致性、隔离性和持久性,简称 ACID。
原子性是指事务包含的操作要么全部成功,要么全部不成功。
比如 A、B 账户的初始余额为 800 元,100元。此时,A 向 B 转账 500 元,那么分解开来就是 A 账户减 500 元,B 账户加 500 元。
最终结果是 A 账户余额为 300 元,B 账户余额为 600 元。这两个账户余额更新的操作,要么全部执行,要么都不执行。
事务执行前,和执行后都会保持一致性状态。
A、B 账户在转账后,会发生两种情况:
钱转到 B 账户里了,这时 A、B 账户分别为 300、600 元;
钱转出去的过程中数据库网络断开,事务回滚了,A、B 账户还是 800、100 元。
无论怎样,事务发生前后,A、B 银行账户的总额都应该为 900 元,这就是前后一致性。
隔离性是当多个用户并发访问数据库时,不管是不是操作同一个库、还是同一张表,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间也要相互隔离。
比如,A 向 B 转账的时候,不管别人怎么转账,都不会影响他们的交易。
一个事务一旦被提交了,那么对数据库中的数据的改变就是持久性的【即保存到磁盘里】,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
拿给张三的例子,持久性就是:你只要给张三转了钱,钱就进了她的账户。
Q:为什么事务有这几大特性?
A:我们要保证事务的数据一致性,就需要一些手段来实现,这几种手段就是事务的几个特性。
它们分别是原子性、一致性、隔离性和持久性,其中一致性是目的,而原子性、一致性和隔离性都是为了实现数据一致性的手段。
并发是指计算机系统或程序在同一时间内同时处理多个任务或操作的能力,也就是允许多个用户进程去处理同一块临界区。
拿转账为例,用户进程就是负责交易的服务器进程,临界区就是张三账户的存储空间。
如果出现了事务并发,就会带来一些意想不到的问题,例如常见的脏写、脏读、重复读和幻读。
脏写是指:在事务并发的时候,一个事务可以修改另外一个正在进行中的事务的数据,这可能会导致一个写的事务会覆盖另外一个写的事务数据。
拿转账为例,假如你给张三转了 500 块,王五给张三转了 1000 块,在写入数据库的时候,你写入的数据被王五的数据给覆盖了。最后导致的结果就是,你钱没了,而张三却只收到了王五的转账。
500 块钱没了怎么办?这时就要用到事务隔离。
MySQL 提供了事务隔离级别,包括:读未提交、读已提交、可重复读以及串行化,来解决事务中各种并发问题,专治各种不开心。
RU - 读未提交(Read uncommitted)
RU(读未提交)是指,如果一个事务开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读取此行数据。RU 可以排他写,但是不排斥读线程实现。这种隔离级别解决了上面的脏写问题,但可能会出现脏读,即事务 B 读取到了事务 A 未提交的数据。
RC - 读已提交(Read committed)
该隔离级别在一个事务进行数据写入时,不允许别的事务对该行数据进行访问(包括读写)。这样就可以保证事务读到的数据一定是已经提交了的,解决了脏读的问题。但是会出现不可重复读的问题,比如:事务 A 需要读取两次数据,在读取完第一次数据后,有另一个事务 B 对该数据进行的更新并提交事务。此时事务 A 再次读取该数据时,数据已经发生了改变,即事务中两次读取的数据不一致。
RR - 可重复读( Repeatable read)
在同一个事务内,多次读取同一个数据,在这个事务还未结束时,其他事务不能访问该数据(包括读写)。这种隔离级别下解决了脏读和不可重复读的问题,但是可能会出现幻读。
如事务 A 在多次读取数据时,有另一个事务 B 在数据行中间插入或删除了数据,此时事务 A 再次读取时,可能会发现数据的行数变了。
简单来说,RR - 可重复读可以保证当前事务不会读取到其他事务已提交的 update 操作,但无法感知其他事务的 insert 和 delete 操作。
可串行化(Serializable)
该隔离级别下,事务只能依次执行,解决了脏读、不可重复读和幻读的问题。但是代价较高,性能很低,一般很少使用。
在这种情况下,每次有事务进来都需要排队等候,直到前面的交易事务完全结束。
数据库通过隔离级别解决了事务并发出现的各种问题:
MySQL 是怎么实现事务隔离性的呢?
答案是加锁。
事务级别越高,解决的并发事务问题越多,同时也意味着加的锁就越多。
锁的个数对比:RU-读未提交 < RC-读已提交 < RR-可重复读 < Serializable-串行化。
但是,频繁的加锁可能会导致读取数据的时候没办法修改,修改数据的时候没办法读取,极大的降低了数据库读写性能,就像串行化的隔离级别那样。
所以,为了权衡数据安全和性能,MySQL 数据库默认使用的是 RR,即可重复读的隔离级别。
参考:微信公众平台