SQL标准中定义了四种隔离级别:未提交读(read uncommitted),提交读(read committed),重复读(repeatable read),串行读(serializable),任何一款数据库都应遵照SQL标准实现这些隔离级别(虽然有一些例外,因为数据厂商都有一些私有的标准。如Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别)。但无论哪种数据库,实现这些隔离级别都采用了一种方法:加锁!加锁的目的是避免数据在并发环境下修改错乱,换句话说就是要求多线程轮流操作共享数据资源。
InnoDB 内部也是通过一系列的锁来实现数据操作的隔离的。对数据的操作无外乎有两种:读和写。结合多种场景,这两种操作在不同的隔离级别下会有不同的行为表现。这四种隔离级别就是对众多不同行为表现的归类,下面对它们一一分析。

  • Repeatable Read
    这是MySQL默认的隔离级别。非加锁读使用快照来实现,即同一个事务内,同一条查询,多次执行,其查询结果是一样的,都是第一次执行时的结果。而加锁读什么是加锁读?如select for update or locak in share mode, update,delete这些都是加锁读!)则比较复杂。如果读语句使用unique key,则InnoDB只使用record lock, 不使用Gap lock, 因为唯一索引保障了数据不会重复,同一条数据的gap不会再插入新的数据;对于其它查询则InnoDB要使用Next-key lock(record lock + gap lock)。这会导致更新一行锁住两行的现象 。
    另外,由于innodb的锁是加在索引上,如果查询/更新没有使用索引则会锁全表。因为没有使用索引字段查询/更新,InnoDB只好使用主键索引,若没有主键,则会使用隐式创建的主键,但不管哪个主键,InnoDB都无法确定更新条目的Gap,既然这样只好锁全表了。
  • Read Committed
    这是大多数数据库采用的默认隔离级别,它满足了大多数应用需求。该隔离级别下,事务内的查询读取各自的最新快照,同一事务内先后执行两条同样的查询,可能会有不同的结果(幻读现象)。对于加锁读,InnoDB仅对index record加锁,也就是仅使用行级锁,而不会锁行前面的Gap,这使得更新的同时允许插入相同的索引条目。但涉及到外键约束和重复键检查时,Gap lock也会用到。
    在复制架构中必须使用row-based binlog logging来保障主从数据的一致,为什么?

    对于update和delete操作,innodb会根据where条件筛选要更新的数据,如果某条数据不符合where条件,则会立即释放掉该行的锁,而且如果查询没有使用索引,innoDB同样需要将锁加在主键上,但不会锁全表,因为它不涉及Gap。这使得并发增强,而且降低来死锁的概率。

    对于update操作,InnoDB可以使用半一致读,来优化查询,提高并发。即,如果要更新的某行处于锁定状态,InnoDB也将其返回给MySQL 来决定此行是不是符合where条件。如果符合条件,则必须更新此行,MySQL会再次查询看是否还处于锁定状态,如果是则等待,如果不是则就加锁然后更新;但如果此行不符合where条件则直接忽略。这也提高了并发的性能!

  • Read Uncommitted
    这种隔离级别使用场景较少,它允许读到未递交事务(脏读),除此之外,它的表现行为与read committed相似。
  • Serializable
    这个与Repeatable Read相似,但当autocommit被关闭时,InnoDB自动将select 语句转换为select .. lock in share mode,这会使得查询时无法更新。如果autocommit开启,则一个查询就是一个事务,且执行完就commit了。

上述是InnoDB的四种隔离级别实现。其中 read committed和repeatable read能够满足绝大多数业务,而read uncommitted和serializable似乎永远不会用到,重点理解前面两种就可以了。