这段时间接触了许多数据库,比如mysql,redis,mongo等。因此生了总结一些笔记的念头,故而打算复习以前学过的一些知识,所以会将以往做的一些笔记记录下来。如果在写博客的时候能够引发相关思考,那便再好不过了。
本篇博客的主人公则是MySQL,主要说一说数据库事务和它的隔离级别。
1.事务
什么是事务呢?官方一些来说,事务就是恢复和并发控制的基本单位。
为什么说是基本单位呢?我们在做一个需求的时候,往往不是一个sql语句就可以完成所有的更改,通常都需要一系列的操作才可以达到预期的目的。而这一系列的操作呢,就可以看作一个事务。或者说,我们可以人为地将它看作一个“原子操作”。类似于+1和-1操作。当一个事务执行之后,就相当于+1,那么我如果后悔了呢?我不想加一了,怎么办呢?那就执行回滚,也就是-1操作。
那么事务有什么用处呢?上面说了,事务是恢复和并发控制的基本单位,那么主要作用自然就是恢复和进行并发控制咯。
举个例子,拿网上购物来说,当我们进行交易的时候,通常需要经过以下几个步骤:
①更改商品库存信息;
②保存客户付款信息,生成订单并将其保存到数据库中;
③更改用户的相关信息,比如购物车信息,下单信息等。
如果事情顺利,那么这一系列操作自然可以顺利执行,也就是说交易成功。但是万一在交易的时候,突然有内鬼怎么办?
哈哈,开玩笑啦。但是你想啊,这一系列的操作,难免会出现一些极端情况,比如突然没有信号,或者手机关机了,又或者库存信息异常了,这都会导致整个交易过程失败。那既然失败了,一些操作结果自然就要不得。比如我这一系列操作有A、B、C,我在C卡壳了,那A、B的执行结果肯定就要不得了。那怎么办,把这一系列操作弄成一个事务呗,要是中间出错了,那就乖乖回滚,撤销之前的操作。要是成功了,那自然就皆大欢喜。
2.事务的属性
在第一点里,我们有说到,我们可以人为地将事务看作一个“原子操作”,这其实是事务的属性之一:原子性。
事务必须满足四个属性,也就是原子性,一致性,隔离性,持久性。也就是我们一直念叨的ACID。
①原子性
什么是原子性呢?很显然,我们一般认为原子是不可再分割的,所以一个事务是一个不可分割的整体。这样的话呢,当执行事务的时候,就避免了只有部分事务完成,也避免了只执行了部分操作而带来的错误。咱要么全部执行,要么全都不执行。
要不然,在上面的网购的例子里,倘若我在第三步的时候嗝屁了,要是不对①②的操作进行撤回,那么数据库里存的数据就是错误的,因为我根本就没有交易成功呀,但数据库里却有我的订单信息,岂不怪哉。
②一致性
一致性,其实不太好理解。我最初接触它的时候,我就在想,到底是什么玩意一致?
后来才发现,一致性指的是,一个事务在执行之前和执行之后,数据库里面的数据,必须保持一致。
什么意思呢,拿银行转账来说,转账前后,这两个账户的金额之和是不变的。这个就是一致性。
③隔离性
隔离,和谁隔离?作为一个风度翩翩的事务,那自然要和其他事务保持一定的距离。因此,隔离性所针对的对象,便是其他的事务,这一般说的是在并发情况下。
也就是说,由并发事务所做出的修改,需要与任何其他并发事务所做出的修改隔离。怎么理解呢?
举个例子,对于一对事物A1和A2,对于A1来说,A2要么在A1开始之前就已经结束了,要么就在A1结束之后才开始执行。
因此,总的来说,当事务查看数据库的时候,数据所处的状态,要么是另一个事务修改它之前的状态,要么就是另一个事务修改它之后的模样。作为一个有素养的事务,才不会去查看中间状态的数据呢。
为啥要这样?当然是为了保证数据的安全性咯。要是出现幻读,那可就不好玩了。
④持久性
持久性,也叫永久性。世事无绝对,既然敢叫持久,那肯定有一些东西。什么叫持久性呢?
对于内存数据库来说,一旦断电或者出现其他故障而关闭,那么这些数据可就没咯。咻~的一下就没了。如果事前没有整个快照或者写入磁盘,而里面又恰好有一些重要数据,那么您就可以准备给自己做人工呼吸了,因为这些数据已成 过往云烟,再也无法回到当初的模样。
而对于Mysql来说,事务的永久性,自然也体现在将数据写入磁盘。内存中的数据都是镜花水月,转瞬即逝,只有写入磁盘的数据,才可算得上持久。对于事务来说,写入磁盘的并不是相关数据,而是对应的操作,update,insert 或者delete。事务完成之后呢,这些操作就会被写入磁盘。倘若你需要恢复操作,那么就可以根据对应的命令,进行手动回滚,这样就可以回到事务执行以前的状态。这里可以了解一下commit和rollback。
在这里,不得不称赞一下数据库日志,前些天我做一些需求, 对一批数据进行一系列规则的操作,大概有六百万数据。由于心大,因此没有做备份。谁知道新来的实习生错误操作,把对应的表删除了,他一脸懵逼的问我怎么办........我??? 好在日志记录了相关命令,发现是删除命令只是简单的delete,所以恢复了过来。否则,产品会发狂,我也会发狂......
3.并发操作所带来的数据不一致性
上面我们说到,事务是并发控制的基本单位,保证事务的ACID自然是义不容辞。但是数据库基本上都是多用户共享数据库资源,也就是说多个用户可以同时操作相同的数据。
如果是串行操作,即事务顺序执行,那自然没啥大问题。但是倘若是并行控制,数据库同时接受多个事务,并发操作有可能会破坏事务的ACID属性。
当多个用户同时访问一个数据库,倘若他们的事务同时操作相同的数据,而又不曾加锁,那么就很有可能会导致数据不一致性。
具体包括:更新丢失,脏读,不可重复读。
①更新丢失
在无锁的并发操作下,如果两个事务同时更新一行数据,一个事务对数据的更新将很大可能把另一个事务的更新覆盖。比如多线程计算的时候,更新丢失很大几率会导致计算结果出错。
②脏读
脏读,很明显,就是说读到了脏数据。什么叫脏数据呢,就是未提交的数据。一个事务读取了另一个事务的未提交的数据,这是很不安全的。万一该事务回滚了呢?这样的话,这个数据就被撤销了。这样的话,脏读之后,该事务的执行结果也不见得正确。
③不可重复读
不可重复读,是针对于读取结果来的。当一个事务对同一行数据读取两次,但却得到不同的结果,这便是不可重复读。
若是细分,则有以下两种情况:
- 虚读:虚读是指一个事务A1读取某一个数据之后,另一个事务A2对数据做了修改,当该事务A1对该数据再次读取的时候,得到的则是不同的结果。
- 幻读:幻读则是说,事务在操作过程中,进行两次查询,由于两次查询之间,有另一个事务进行了插入或者删除操作。因此第二次查询的结果包含了第一次查询中没有出现的数据,或者缺少了第一次查询中出现的数据。比如说,线程A在不停获取某表中的前十行数据,而线程B则不停在删除第一行数据。这样的话,线程A的每次检索结果,都有可能与上一次不相同。
4.事务的隔离级别
第三点说的这几种情况,乍一看,似乎觉得数据库用起来很不安全。不过,作为一个风度翩翩的数据库,自然会有相应的对策去避免以上的几种情况咯。
在标准的SQL规范中,定义了四个事务隔离级别,来避免上面的几种情况。而不同的隔离级别,对事物的处理也是不同的。下面就来认识一下:
- 读未提交:只处理更新丢失。如果一个事务已经开始写数据,此时不允许其它事务进行写操作,但是允许其它事务对该行数据进行读操作。可通过“排它写锁”来实现。
- 读已提交:处理更新丢失,脏读。读取数据的事务允许其它事务继续访问该行数据,但是没有提交的写事务会禁止其他食物访问该行数据。可以通过“瞬间共享读锁”和“排它写锁”实现。
- 可重复读:处理更新丢失,脏读和不可重复读。读取数据的事务会禁止写事务,但是允许读事务,写事务进行的时候禁止任何其它食物。可以通过“共享读锁”和“排它写锁”来实现。
- 序列化:提供严格的事务隔离。序列化也叫可串行化,要求失去序列化执行,这样的话,事务就变成了串行执行,不能并发执行。这里需要注意一点,仅仅通过“行级锁”是无法实现事务序列化的,必须通过其它机制来保证新插入的数据不会被刚执行查询操作的事务访问到。
事务的隔离级别越高,就越能够保证事务的完整性和统一性。当然,没有完美的盛宴,级别越高,对并发性能的影响也就越大。对于MySQL来说,默认的隔离级别是可重复读,正好可以避免更新丢失、脏读、不可重复读。对于其他的一些数据库,比如Oracle,一般将隔离级别设置为读已提交,它可以避免脏读,而且拥有较好的并发性能。而对于不可重复读、脏读、第二类丢失更新等这些并发问题,则可以适当地用悲观锁或者乐观锁来进行控制。