数据库事务总结与整理(一)

前言

提数据库就会讲到数据库的事务问题,常见的一个问题就是:你的数据库是如何保证事务的?这里的数据库可以是关系型数据库,如mysql,或者是非关系型的,如MongoDB或是其他数据库。令人头疼的是,这样的问题很常见却不知怎么回答,一个原因是没有涉及过事务相关的操作,还有项目中有事务,往往也是一个注解搞定,根本不深入。今天就来全面总结下有关事务的问题。

事务的若干概念

ACID特性

一般来说,事务具有4个特点,A(原子性),C(一致性),I(隔离性,事务之间具有一定隔离性),D(持久性),称为ACID特性。

原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。

一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。

隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。这里的不影响具体要看具体的事务隔离级别。

持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

数据库的原子性,一致性和持久性一般通过事务日志实现,而隔离性通过数据库锁、MVCC(并发一致性控制)实现。

事务隔离型

事务定义了四种隔离级别。分别是未提交读已提交读可重复读串行化

  1. 读未提交(READ-UNCOMMITTED)
    所有事务都可以看到其他未提交事务的执行结果,可能产生脏读,一般(十分)不采用。

  2. 读已提交(READ-COMMITTED)
    其他事务提交后才可以看到,避免了脏读现象。但仍避免不了这个问题:我一个事务中第一次某行数据是一个值,第二次读又是一个值了。(在两次读过程中,有事务提交了),这种现象叫不可重复读。顾名思义,就是不能重复读取。
    读已提交是大多数数据库默认的隔离级别(不包括mysql,mysql隔离级别是可重复读),因为这已经满足隔离的基本定义,性能也很高。

  3. 可重复读(REPEATABLE-READ)
    针对前一种隔离级别的问题,修复了不可重复读的问题,也就是在一个事务中,读取同一行的数据是不变的。不过,有个问题是:当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。这是幻读
    可重复读是mysql默认隔离级别。

  4. 串行化(SERIALIZABLE)
    所有事务操作串行执行,没有隔离级别引起的问题,但性能也最差,一般不用。

设置mysql隔离级别

set session transaction isolation level read committed;

mysql的锁

按锁的粒度来分,分为表锁和行锁。

行级锁是Mysql中锁定粒度最细的一种锁,只针对行加锁。行级锁能大大减少数据库操作的冲突。加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。

表级锁是MySQL中锁定粒度最大的一种锁,表示对整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。

MySQL不同的存储引擎支持不同的锁机制。如MyISAM支持表级锁,而InnoDB支持行级锁和表级锁(默认支持行级锁)。

MyISAM

MyIsam支持表级锁,表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。也就是读锁和写锁。其中读读之间可以共存,读写之间互斥。

MyIsam的读、写锁都是阻塞锁,即如果拿不到锁,会一直阻塞住。

加锁方式是一次性拿到所有的锁,所以不会出现死锁现象。一般select会自动加读锁,updateinsertdelete会自动加写锁。另外,即是同一个表,如果在sql中出现多次,也会加多个锁。

默认写锁的级别比读锁高,当有大量读、写任务时,读锁可能会一直阻塞获取锁。这也正是 MyISAM 表不太适合于有大量更新操作和查询操作应用的原因

InnoDB

InnoDB默认为行锁,表锁前面已经说了,现在说说行锁。行锁种类和表锁类似,也分别读和写锁。默认分为共享锁(S,也就是读锁)和排他锁(X,写锁)。

共享锁,行锁加共享锁使用SELECT ... LOCK IN SHARE MODE。共享锁之间可以并发。

排他锁,使用for update进行加锁。排他锁与其他锁互斥,加排他锁后不能再加其他锁。

select .... for update 就是对读操作加了排他锁。

其他 update、delete、insert等写操作,会自动加排他锁。

行锁是一行一行进行的,分别取出来一一加锁。

MVCC(多版本并发控制)

MVCC是数据库提高读、写并发的常用技术,简单的说就是通过设置多版本,在读取数据时根据事务开始时机以及隔离级别读取对应版本的数据,可以让读、写互不阻塞。大大提高性能。

MVCC中读分为快照读和当前读。快照读就是读取历史版本,当前读就是读取当前数据。

普通的读都属于快照读,如select ...

属于当前读的有(加锁操作和写操作)

select ... lock in share mode 加行共享锁
select ... for update 加行排他锁
update ...
delete
insert into

加锁分析

在innodb下分析

  1. 普通的select 不加锁,直接走快照读。
  2. updatedelete操作,在RC(读已提交)情况下,有主键走主键(加X锁),有索引走索引(唯一索引或普通索引,加锁两次,一次加在二级索引上,一次加在主键索引上,唯一索引锁一行,普通的锁符合条件的行数,加X锁)
  3. 对于updatedelete,没有索引的情况,在RC(读已提交)下,会扫全表加锁,然后对不符合条件的释放锁。(全部加锁这一步是不能少的,也即最终是对符合条件的行加的行锁)
  4. 对于updatedelete在RR(可重复读)情况下,查询条件是索引,但不是唯一索引的情况下,会在索引中加X锁和间隙锁(GAP), 在主键索引中加X锁。GAP锁是为防止记录之间插入记录。
  5. 对于updatedelete,在RR下,查询条件不是索引,扫描主键索引,在主键索引所有记录上加X锁,并在记录之间加GAP(间隙锁)。
  6. 对于where条件,多余1个情况下,可分为Index keyIndex Filtertable filter。有索引确定查询条件(会添加间隙锁),index filter 对索引条件进行过滤,过滤后加上X锁,table filter 会始终加上X锁。有mysql引擎层过滤。
  7. GAP(间隙锁)存在于RR(可重读读)级别及以上,读未提交(RU)和读已提交(RC)是没有间隙锁的。

再次总结下,在RC(读已提交)级别下,当需要加锁时(写操作或使用for update、share in lock mode),无论有无索引,最终加的都是行锁,不过,有索引会避免全表扫描,直接在索引行加锁;而无索引的话则会先全表扫描(这一步不可少),然后由mysql引擎过滤掉不符合条件的行,加上行锁。

在RR(可重复读)级别下,区别在于有了间隙锁,不过原则是如果能查到唯一一行,则加行锁,多行的话,则是行锁+间隙锁。如果是聚簇索引,则一颗索引树,非聚簇索引则非聚簇和聚簇树都要分析下。

发生死锁的条件不在于sql的多少,而在于加锁的顺序,比如一个sql先对A行加锁,后对B行加锁,而另一个sql则相反,则可能会产生死锁。具体可看MySQL 加锁处理分析 写的很详细。

隔离性实践

读已提交情况下

事务A事务B
begin;begin;
select score from grade where id=1; // id为1的分数为50
update grade set score =100 where id=1;
select * from grade where id=1; // 这里成了100
commit;commit;

如果改为可重复读,则结果一直是50。

参考文章

  1. 互联网项目中mysql应该选什么事务隔离级别
  2. 彻底理解数据库事务
  3. MySQL 加锁处理分析
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值