事务
在介绍事务之前,我们先来了解一个案例:
在一个买票的软件中,当客户端A检查还有一张票时,将票卖点,但是还没有更新数据库,客户端B检查了票数,发现大于0,于是又卖掉了一张票。然后A将票数更新回数据库,这样就出现了同一张票被卖了两次。
为了避免为了避免这样的情况出现,所以,就出现了事务。
目录
什么是事务?
为什么会出现事务
隔离性、并发性
脏读
不可重复读
幻读
什么是事务?
事务就是一组DML语句,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败。比如:你的各科成绩,你在校表现,甚至你在论坛发过的文章等。这样,就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务。概括一下就是,多条SQL语句打包在一起,成为了一个事物。
但是,如果一个事物在执行到一半的时候出错或者不再想继续执行了,那么已经执行的怎么办呢?所以,一个完整的事务,还需要满足以下几个属性:
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中 间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)(类似于电脑上的Ctrl+Z)到事务开始前的状态,就像这个 事务从来没有执行过一样。
原子性是事务的初心。
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工 作。
一致性是事务要解决的问题、产生的结果。
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务 并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化 ( Serializable )。
与其对应的是并发性,我们平常会听说高并发等概念,其实就是说的这个并发性。数据库的隔离性越低,那么数据库的并发性就越高。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。事务产生的修改,都是会写入硬盘的,对于程序重启、主机重启、掉电等等,事务都可以正常工作,保证修改是生效。
为什么会出现事务
事务被 MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,,不需要我们去考虑各种各样的潜在错误和并发问题。可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服务的,而不是伴随着数据库系统天生就有的。
隔离性、并发性
mysql服务器要同时给多个客户端提供服务,此时多个客户端之间,可能会同时发起事务,尤其是这多个事务在操作同一个数据库的同一个表的时候,就很有可能引起一些麻烦。
于是数据库的隔离性和并发性就引入进来,
如果隔离性越高,就意味着事务之间的并发程度越低。执行效率是越慢,但是数据的准确性是越高的。
如果隔离性越低,就意味着事务之间的并发程度越高。执行效率是越快,但是数据的准确性是越低的。
同时mysql给我们提供了不同的档位,可以控制隔离性/并发程度/执行效率/数据准确性的高低。
但是注意!隔离性越高,那么并发性就会降低,反之亦然,这两者是需要进行权衡的,如何取舍,需要根据实际需求,具体问题具体分析。
脏读
脏读:脏读就是读取未提交的数据。脏读指事务A读取到了事务B更新了但是未提交的数据,然后事务B由于某种错误发生回滚,那么事务A读取到的就是脏数据。
具体的说,一个数据原本是干净的,但是事务B将它进行修改使得其不再干净,此时事务A读取到事务B修改后的数据,也就是脏数据,称为脏读,后来事务B由于良心发现又将数据回滚为最初的样子,而事务A不知道事务B进行了回滚操作,最终事务A读取到的是脏数据,称为脏读。
例如:我在写一份代码,我的同学看到后把我的代码抄完了,但是我发现这个代码是错误的,于是我就把这份代码删除了。在这个场景中,我的同学和我的这俩事务,是完全并发的,没有任何限制。在这个场景下就会出现脏读问题。
如何解决:我写代码的时候,我不让我的同学来看,等我把代码上传后再允许他看。也就是说给我的代码加锁。也就是“写加锁”。我写的时候,同学就不能同时去读取了,这样就相当于降低了并发性,提高了隔离性,降低了一定的效率,但是提高了准确性。
不可重复读
不可重复读:前后多次读取,数据内容不一致!
不可重复读指在数据库访问时,一个事务在前后两次相同的访问中却读到了不同的数据内容。
比如说事务A的执行周期较长,事务A在第一次读取某个数据时,此数据的值为100,读取完成后,事务A又去执行其他的事情,在这个过程中,事务B将这个数据的值修改为200,然后事务A做完其他的事情后,又来读取这个数据的值,发现这个值和第一次所读取的值不相同,这种现象称为不可重复读。
如何解决:给读操作也加锁,别人在读代码的时候,我不能修改~这两个事物之间的并发程度进一步降低了,隔离性又进一步提高了。运行速度又进一步变慢了,数据的准确性又进一步提高了。
幻读
幻读:幻读指的是事务A在查询完记录总数后,事务B执行了新增数据的操作,事务A再次查询记录总数,发现两次查询的结果不一致,平白无故的多了几条记录,这种现象称为幻读。
如何解决:串行化,彻底舍弃并发。
幻读和不可重复读的本质是一样的,两者都表现为两次读取的结果不一致。但是不可重复读指的是两次读取同一条记录的值不同,而幻读指的是两次读取的记录数量不同。
不可重复读重点在于update和delete,而幻读的重点在于insert。
MySQL提供了四个隔离级别:
1.read uncommitted
不做任何限制 事务之间都是随意并发执行的 并发程度最高 隔离性最低 会产生脏读+不可重复读+幻读
2.read committed
对写操作加锁了 并发程度降低了 隔离性提高了 ―解决了脏读问题 仍然存在不可重复读+幻读
3.repeatable read(默认档位)
对写和读都加锁了 并发程度又降低了 隔离性又提高了 解决了脏读 不可重复读 可能存在幻读问题
4.serializable
严格串行化 并发程度最低(串行执行的)隔离性最高 解决了脏读+不可重复读+幻读问题 执行速度最慢的
开发中可以根据当前要解决的问题的需求场景,来决定使用哪个级别。这一些级别通过MySQL的配置文件来修改。
在这里就有一个经典的面试问题:MySQL的四个隔离级别都是干啥的,可能产生的问题,以及每个问题的解决方案是什么~