数据库——事务
事务是指作为一个整体被执行的一系列操作。在数据库管理系统中,事务是指一组数据库操作(如插入、更新、删除等)的逻辑单元,也就是说事务的本质是把多个操作打包成一个操作,并且它要么完全执行,要么完全不执行,而且它的完全不执行并不是真正的完全不执行,而是如果在某个操作的过程出现错误,就会将前面的操作全部回退(也称回滚),使之回到还没开始执行的状态。那么回滚是如何使它返回到未执行的状态呢?其实只是把事务中执行的每个操作都记录(通过特定的日志,记录数据库事务操作的中间过程)下来,如果需要回滚,只需按照之前的操作的逆操作来执行就行,例如前面的操作是插入数据,它的逆操作就是删除数据。它还具有以下4个特性:
1)原子性:事务被视为一个不可分割的最小单位,要么全部执行成功,要么全部回滚到最初状态。回滚保证事务的原子性,而回滚则是通过特定的日志来保证。start transaction是事务的开始,接下来的sql语句就将是事务的操作,commit(提交事务)或 rollback(手动触发回滚)是事务的结尾,commit是全部成功,rollback是全部失败。一个事务务必要以这两个中的一个操作进行结尾。如果没有这两操作,接下来的sql语句都会被认为是这个事务的一部分。
2)一致性:事务执行前后,数据库从一个一致的状态转换到另一个一致的状态。事务执行过程中可能会破坏一致性,但在事务结束时必须保证恢复到一致的状态。也就是事务执行前后,数据能够对的上(例如转账,一个增,一个减,并且结果相同)。通过约束和回滚来保证事务的一致性。
3)持久性:一旦事务提交成功,其所做的修改将永久保存在数据库中,即使系统发生故障也不会丢失。
4)隔离性:并发执行的事务之间应该相互隔离,每个事务都感觉不到其他事务的存在,以避免数据不一致或读取脏数据,并且隔离性越低,效率将会增加,数据也就越不可靠,隔离性越高,效率也就减低,数据也就越可靠。例如:
有着客户端1和客户端2,它们同时操作一个表,客户端1对表格进行查询,如果大于100,就对它进行判定,而客户端2对该表格进行加200,它们同时将事务提交给MySQL数据库,那么就会发生以下情况,先进行查询,后进行加200,然后得出判断,或者先进行加200,然后查询,在判断,这样就出现两种结果,那么这两种结果那个是可靠的呢?并且为什么会出现这种情况呢?可不可靠根据情况来看,但出现这个原因还是因为虽然这两个事务是同时提交的,但处理事务总是会有一个时间差,哪怕它在小,这就导致会出现这种情况,这就会出现错误,因为得到的结果是随机的,有的时候这个结果是正确的,但有时又是错误的。
例子1:当老师正在批改试卷,一个同学过来看,发现他是100分,然后就走了,但老师在批改过程中,发现改错了,然后就改回来,这个同学就得到了临时的分数,不是最终的分数,等到试卷发下来后,就发现怎么与之前看到的分数不同。而这种临时的分数,也可以说是临时的数据就称为脏数据,读取临时的数据就称为脏读。那么如何解决脏读的问题呢?很简单,那就是给写操作(改试卷)加锁,写的时候不让其他人读(看分数),写完之后才让读取,这样读取的数据就是最终的数据,这就解决了脏读的问题,引入写加锁后,就提高了两个事务(一个改试卷,一个看试卷)的隔离性,减低了两个事务的并发性,降低了效率,却提高了数据的准确性。这个问题解决后,
例子2:第二个问题又出现了,这时老师与同学们说,在我改完之后,你们不许看后,等老师改完后,同学们跑去看试卷,老师突然想到又改错了,有跑回去改了,因为只约定了改时不能看,没约定看时不能改,因此老师便在同学们看时就去修改,边看边上交,然后同学们发现,怎么看着看着分数就变了呢?那么学生就会怀疑老师的水平,这种情况肯定是非常不好的。这种问题就被称为不可重复读(在同一个读取数据的事务中,可能会涉及到多次读操作,但多个读操作得到的数据不一样)。解决这个问题就很简单,只需要再给读操作加锁就行。这样就会导致别人读到的数据就不会改变,等老师再去修改时,就不能上交结果,必须等同学读完才能上交修改结果,等同学再次去读时,就会读到修改后的数据,也就是之前同学读的是旧数据,现在同学读的是新数据。这个旧数据可不是脏数据,因为它之间可能隔了许多过程,可以认为是两个版本的数据,一个是新版本的数据,一个是旧版本的数据,就跟游戏角色增强或削弱类似。这样并发程度又进一步降低了,隔离性又提高了,效率又降低了,数据又更可靠了。
例子3:这时第三个问题了,这时老师和同学们约定,改时不能看,读时不能改,但是因为同学们时看试卷时,老师觉得闲着也是闲着,就去帮别的老师批改试卷,并将这份试卷放在同一个位置,这时同学们就会发现怎么每次读时,都会多出几份新的试卷,这就会让同学觉得自己可能脑子不好,怎么每次都会少读取几份试卷。这种情况就称为幻读(一个数据再多出读操作过程中,虽然每次读的数据的值是相同的,但结果集不同,也就是会多出一个或几个)。既然读的时候不让老师改试卷,那么老师就再去改别班的试卷,这就导致每次读的时候会多出几分试卷。可以看出,幻读是不可重复读的特殊情况。那么如何解决幻读呢?办法只有一个,那就是彻底放弃并发执行事务,将执行事务串行化,所有事务都是一个挨着一个,不会出现在读的过程中,再去写别的或这个的情况,也就是流水线,必须先做完前一个,才能进行下一个,不能再等前一个的过程中做别的事情。而这个的并发性是最低的,隔离性是最高的,效率最低的,数据最可靠的。
总结,在并发执行事务的过程中,可能会发生以下问题
1)脏读:读到了写数据之前的中间数据(脏数据)
2)不可重复读:在同一个读取数据的事务中,可能会涉及到多次读操作,但多个读操作得到的数据不一样
3)幻读:一个数据再多出读操作过程中,虽然每次读的数据的值是相同的,但结果集不同,也就是会多出来一个或几个
对于这种情况,MySQL提供了4种事务的隔离级别
1)read uncommitted:允许读未提交的数据。(脏读,不可重复读,幻读的问题仍然存在,并发程度最高,效率最高,隔离性最低,数据可靠性最低)
2)read committed:允许读已提交的数据。(给写操作加锁,解决了脏读的问题,但不可重复读,幻读的问题仍然存在,并发程度降低,效率降低,隔离性增加,数据可靠性增高)
3)repeatable read:可以重复读取数据(给写操作和读操作加锁,解决了脏读,不可重复读的问题,但幻读的问题仍然存在,并发程度再次降低,效率再次降低,隔离性再次增加,数据可靠性再次增高)
4)serializable:事务彻底的串行执行,(脏读,不可重复读,幻读的问题,并发程度最低,效率最低,隔离性最高,数据可靠性最高)
所以可以根据需求来选择使用那个隔离级别,大部分情况下,使用默认隔离级别就能够解决,而repeatable read就是默认级别。