mysql事务概念

1. 事务的概念

   事务是一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务。在SQL层面可以理解为一个事务,是由多条SQL组成用以完成一个业务功能的共同体,事务影响到SQL的CRUD等操作。仅INNODB搜素引擎支持事务,在MYISAM中不支持事务,但在MYISAM中使用事务操作如BEGIN和COMMIT不会报错,使用ROLLBACK无效果(不能回滚)。如果发现在代码中使用了事务但不生效的情况,可以优先查看使用到的表是否均已经使用到了事务。

2. 事务的特征(ACID)

  A:原 子性(Atomicity,或称不可分割性)

       事务中所有的SQL作为一个整体,要求所有的SQL都全部执行通过COMMIT正常提交,如果中间的某个环节,即某条SQL错误,而抛出异常,则要使用ROLLBACK回滚事务。如果未回滚事务,则该事务作为一个废弃的事务一直存在,如果存在多个废弃的事务会影响数据库性能。在MYSQL中,如果未显式的开启事务,则认为每一条MYSQL的语句均为一个事务。

C:一致性(Consistency)

      事务在开始之前和开始之后,对数据表的记录的操作,都是正常的,原本数据表能正常工作,修改后数据表也能够正常工作。

I:   隔离性(Isolation,又称独立性)

     数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。数据库的隔离性分为读未提交(Read uncommitted)、读已提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

D:持久性(Durability)

     事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。可以简单的认为事务执行完成后就相当于落盘了,即使数据库服务器宕机了,重启后,已经提交的事务所做的修改依旧有效。

备注:MYSQL的myisam和innodb引擎中,仅innodb引擎支持事务,myisam不支持事务。

3. 事务的隔离级别

 相关概念:

 脏读:一个事务读取到别的事务中未提交的事务,叫做脏读。

 不可重复读:一个事务在同一条记录中在事务的生命周期内,如果未对该条数据做修改,则多次读取的时候,应该与第一次读取的记录相同(是第一次SELECT中,而非事务BEGIN(开启)的时候,如果在事务BEGIN和SELECT过程中,有别的事务修改并提交了该条记录,则第一次SELECT查询出来的是最新的已经提交过的该条记录的结果。

  幻读:在查询过程中多出了数据记录或者少了数据记录,(受到DELETE和INSERT)的影响,例如SELECT * FROM PERSON WHERE AGE > 80;会返回多条查询记录,加入是50条,此时有别的事务插入了一条人员或者删除了一条人员信息,再次查询只有51或者49条了,就像出现了幻觉一样。又例如:在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务-->修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务-->事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作

  隔离级别说明:

   读未提交(Read uncommitted)

    顾名知义,读到了别的事务中未提交的事务中的数据,该种级别下无法预防脏读、不可重复读和幻读,该种级别是未被MYSQL加锁的。

    读已提交(read committed)

     顾名知义,在事务A中读到了别的事务已经提交的数据,例如开启了事务,然后第一个SELECT按照某些查询条件查询出了一些记录,如果此时事务B修改了这些记录,并已经提交。则事务A读到了事务B提交的记录修改后的数据。造成了事务A两次读取相同的记录的结果不一致。该种级别可以预防读到未提交事务的修改数据,但无法预防不可重复读和幻读 未加锁。

    可重复读(repeatable read)

     顾名知义,在事务A中,只要未修改某一条记录,则第一次SELECT和最后一次SELECT的结果始终相同。如果在事务A自身对该条记录做了修改,则会将修改存储到内存中,仅当修改提交后再落盘(可以简单的认为是落盘)。该种级别可以预防脏读,和不可重复读,但无法预防幻读。此种级别是MYSQL的默认事务隔离级别,MYSQL使用MVVC(多版本并发控制)来预防幻读。未加锁。在此种隔离级别下:因为事务在可重复度的前提下,无法在该事务中获取在事务执行过程中,别的事务修改的该事务要操作的记录,就造成了并发问题。会出现的常见场景如,购买商品等库存修改问题,例如常见的在一个事务中先查询库存数目,然后再将库存数据减1,例如SELECT * FROM goods where id = 1;判断库存是否够,UPDATE goods SET STOCK = STOCK - 1 WHERE ID= 1;然后提交。因为在查询SELECT之后到UPDATE这段时间中别的事务已经修改了库存数目例如将库存从10修改为9,而本次SELECT也查询到库存是10,也修改为9,则库存减去1,实际已经发生了两次下单的行为了。这就是并发问题。

       注意:

        (1): MYSQL的更新操作是串行化的,如果先SELECT库存数,例如为10,然后执行UPDATE goods SET STOCK = STOCK - 1 where STOCK = 9;这个时候就不会发生并发问题,其原理和乐观锁相似,本质上是利用了修改的阻塞操作,在MYSQL的修改相同的记录都是串行化的,可以认为MYSQL在修改这条记录的时候加入了锁,别的事务如果此时也修改了相同的记录,则会一直等待到本事务提交后才能进行。这个时候本次事务的WHERE查询和UPDATE不是分开进行的,可以理解为是一次执行的,是一个整体。注意UPDATE的WHERE和SELECT中的WHERE的区别,此时UPDATE的WHERE是查询到所有已经体提交的记录,即使在本次事务执行中还未开始,或者刚刚开始的事务,只要这些事务对这条记录做了修改并已经正确提交,则UPDATE的WHERE是可以获取到这些已经修改的记录的(你可以认为此时发生了不可重复读了),而SELECT中的WHERE是读取不到别的事务在SELECT之后修改的数据记录的。(此种情况对DELETE同样适用)

       (2):MYSQL中的更新阻塞在表现上看就是事务A开始事务更新后,如果在代码中SLEEP了20秒,则事务B在更新相同记录的时候必须等事务A提交后再进行,事务B此时的UPDATE会一直等,事务A提交后,事务B也提交了修改。

        (3):在MYSQL中事务是不允许嵌套的,即一个事务嵌套另外一个事务,否则会报错。在LARAVEL框架中处理很简单粗暴,有个属性记录事务次数,如果超过了1次则不执行提交和回滚操作。在PDO中如果嵌套会直接报错。

         (4):事务的并发问题的思路,

            a. 提供一个条件在开始查询库存的时候的就序列化查询操作,在第一次查询时候就上锁,使得别的查询不能上锁,必须等本次事务提交后查询才能进行。这种思路下如加悲观锁select for update,给一个文件上写锁在事务执行完成后再解锁。此种思路下,本质是利用了加锁操作是串行的,即事务A加锁和事务B加锁必须排队进行。但即使对数据查询记录加了锁,并不影响到别的事务的查询,即别的事务依旧可以查询,如果事务A使用for update加锁,然后获取库存S1,将库存减去1,S1-1,然后提交。而此时事务B也要操作库存,也使用for update来加锁,但是此时A并未释放排他锁,则事务B只有等待事务A释放排他锁后才能加锁成功,就解决了并发问题。如果事务B未加锁直接使用SELECT查询出库存,例如S1,然后直接修改S1-1然后提交。这时候要看事务A和事务B谁的提交先进行,如果事务A先提交,将库存从S1修改为S1-1。然后事务B执行也是讲库存从S1-1,此时依旧发生了并发修改问题,反正一样。可见使用了FOR UPDATE加悲观锁解决了并发问题的本质是阻塞了别的事务来加锁。

          注意:MYSQL 使用了select for update后如果走了索引会锁住走索引查询到的所有记录,别的事务无法修改这些记录,如果没有走索引则会锁住全表。

            b. 利用修改操作在MYSQL中是串行化的特性,即事务A和事务B都使用SELECT获取了库存S1,然后事务A在修改的时候使用了条件WHERE STOCK = S1(如UPDATE GOODS SET STOCK = S1 -1 WHERE STOCK = S1) 或者使用了乐观锁 先获取了版本号V1 然后在UPDATE的时候加入提价VERSION = V1(如UPDATE GOODS SET STOCK = S1 -1 , VERSION = V1+1 WHERE VERSION = V1) 。而如上所述,MYSQL的WHERE查询是查询到执行UPDATE之前最新一次事务提交的记录,感觉发生了不可重复读一样,此时如果别的事务修改了版本或者库存,并成功提交,则本次事务就可以查询到,并且UPDATE之后的查询条件就不成立了,此时就成功阻止了并发问题。

           c. 利用第三方工具实现操作的串行化,例如将库存塞到REDIS中,因为REDIS是单线程的(单进程?未验证),在REDIS中所有操作可以看成是顺序执行的,事务A从队列中取出了库存的记录,事务B就取不到了,则利用该特性也可以阻止并发问题。常见的是将N个奖品都塞入队列中,如N个就插入奖品ID到队列N次,每次购买取出1个,队列长度变为N-1,直到最后一个事务A取到了,则事务B就取不到了,就阻止了并发问题。               

    串行化(Serializable)

     顾名知义,即所有的SQL操作均是顺序执行,可以理解为将所有的SQL操作排队插入一个队列中然后顺序执行。为事务隔离最高级别,可以预防脏读、不可重复读和幻读,加锁,事务序列化执行,但效率低。

 

 

 

 

 

 

 

你可能感兴趣的:(mysql)