以 begin / start transaction 开始,commit / rollback 结束的事务。或者是带有保存点 savepoint 的事务。
2. 链式事务
一个事务在提交的时候自动将上下文传给下一个事务,也就是说一个事务的提交和下一个事务的开始是原子性的,下一个事务可以看到上一个事务的处理结果。MySQL 的链式事务靠参数 completion_type 控制,并且回滚和提交的语句后面加上 work 关键词。
3. 嵌套事务
有多个 begin / commit / rollback 这样的事务块的事务,并且有父子关系。子事务的提交完成后不会真的提交,而是等到父事务提交才真正的提交。
4. 自治事务
内部事务的提交不随外部事务的影响,一般用作记录内部事务的异常情况。MySQL 不支持自治事务,但是某些场景可以用 MySQL 的插件式引擎来变相实现。
分别是原子性、一致性、隔离性、持久性。
原子性是指事务包含的所有 *** 作要么全部成功,要么全部失败回滚,因此事务的 *** 作如果成功就必须要完全应用到数据库,如果 *** 作失败则不能对数据库有任何影响。
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。
隔离性是当多个用户并发访问数据库时,比如同时 *** 作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的 *** 作所干扰,多个并发事务之间要相互隔离。关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的 *** 作。例如我们在使用JDBC *** 作数据库时,在提交事务方法后,提示用户事务 *** 作完成,当我们程序执行完成直到看到提示后,就可以认定事务已经正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成。否则的话就会造成我们虽然看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。这是不允许的。
在数据库 *** 作中,在并发的情况下可能出现如下问题:
正是为了解决以上情况,数据库提供了几种隔离级别。
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted(未授权读取、读未提交)、Read committed(授权读取、读提交)、Repeatable read(可重复读取)、Serializable(序列化),这四个级别可以逐个解决脏读、不可重复读、幻象读这几类问题。
虽然数据库的隔离级别可以解决大多数问题,但是灵活度较差,为此又提出了悲观锁和乐观锁的概念。
悲观锁,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制。也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统的数据访问层中实现了加锁机制,也无法保证外部系统不会修改数据。
商品t_items表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单(此时该商品无法再次下单),那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。
如果不采用锁,那么 *** 作方法如下:
但是上面这种场景在高并发访问的情况下很可能会出现问题。例如当第一步 *** 作中,查询出来的商品status为1。但是当我们执行第三步Update *** 作的时候,有可能出现其他人先一步对商品下单把t_items中的status修改为2了,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。
在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出t_items信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为t_items被锁定了,就不会出现有第三者来对其进行修改了。需要注意的是,要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新 *** 作后,MySQL会立刻将结果进行提交。我们可以使用命令设置MySQL为非autocommit模式: set autocommit=0
设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:
上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交。
上面的第一步我们执行了一次查询 *** 作: select status from t_items where id=1 for update 与普通查询不一样的是,我们使用了 select…for update 的方式,这样就通过数据库实现了悲观锁。此时在t_items表中,id为1的那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。需要注意的是,在事务中,只有 SELECT ... FOR UPDATE 或 LOCK IN SHARE MODE *** 作同一个数据时才会等待其它事务结束后才执行,一般 SELECT ... 则不受此影响。拿上面的实例来说,当我执行 select status from t_items where id=1 for update 后。我在另外的事务中如果再次执行 select status from t_items where id=1 for update 则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果我是在第二个事务中执行 select status from t_items where id=1 则能正常查询出数据,不会受第一个事务的影响。
使用 select…for update 会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键或者索引,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。举例如下:
1、 select * from t_items where id=1 for update
这条语句明确指定主键(id=1),并且有此数据(id=1的数据存在),则采用row lock。只锁定当前这条数据。
2、 select * from t_items where id=3 for update
这条语句明确指定主键,但是却查无此数据,此时不会产生lock(没有元数据,又去lock谁呢?)。
3、 select * from t_items where name='手机' for update
这条语句没有指定数据的主键,那么此时产生table lock,即在当前事务提交前整张数据表的所有字段将无法被查询。
4、 select * from t_items where id>0 for update 或者 select * from t_items where id>1 for update (注:>在SQL中表示不等于)
上述两条语句的主键都不明确,也会产生table lock。
5、 select * from t_items where status=1 for update (假设为status字段添加了索引)
这条语句明确指定了索引,并且有此数据,则产生row lock。
6、 select * from t_items where status=3 for update (假设为status字段添加了索引)
这条语句明确指定索引,但是根据索引查无此数据,也就不会产生lock。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以只会在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回用户错误的信息,让用户决定如何去做。实现乐观锁一般来说有以下2种方式:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)