ACID
当人们提到数据库事务的特性时, 总会提到ACID. 但是ACID是描述性的词语, 究竟什么是ACID, 缺少严格的定义. 所以我们在说事务要满足ACID特性时, 总觉得再说, 我们要科学发展, 但什么才是科学的, 却模糊不清.
- 原子: 针对对于回退来说的, 可以将变更全部提交, 也可以全部放弃
- 一致: 对于数据的约束不能违反
- 独立: 仿佛每个事务, 一个一个, 顺次对数据库执行操作
- 持久: 写入的数据不因软件/硬件的故障而失败, 也就是说任何变更都要落地到磁盘, 从磁盘文件中恢复回来
我理解, ACID应该是一个模糊的需求描述, 难以下严格的定义. 具体
事务, 解决的问题以及概念 (接地气的解释?)
SQL标准中, 列出了事务操作中存在的几种现象:
- 脏读 (Dirty Read): t1修改了一行数据, t2在t1回滚前读取了改行数据, 从而看到了它不该看到的内容.
- 非重复读 (Non-repeatable): t1读取了一行数据, 之后t2修改了改行数据并提交了, t1再去读改行数据时, 发现变化了!
- 幻读 (Phantom): t1根据条件选择出多行数据, t2插入了一行满足t1选择条件的数据, 这时t1根据相同条件再次选择数据时, 就会发现返回数据多了! 这里选择操作可能隐含在一个条件更新里面, 从而导致修改了没有看到的数据.
根据上述三种现象, SQL标准划分出下述四种隔离级别:
- READ-UNCOMMITTED: 基本没有事务特性, 但是不代表没有, 因为还可以回滚.
- READ-COMMITTED: 不允许脏读. 没有commit的数据, 对外是不可以见的.
- REPEATABLE-READ: 不允许非重复读. 读出来的数据, 在事务结束后都是不可以被修改的. InnoDb默认事务级别.
- SERIALIZABLE: 不允许幻读. 可以理解为所有满足条件选择谓语过的数据, 是不可以变动的, 这里主要是指新增, 因为已满足条件的数据不可以变动了.
另外, 没有覆盖在SQL标准里面, 但是经常出现的问题:
写丢失 (Lost Update) 与 游标稳定 (Cursor Stability)
- 写丢失 (Lost Update): 也就是并发时候经常出现的先读后写的问题. t1读X=10, t2读X=10, t1为X加1并提交, X=11, t2为X+1并提交, X=11; 从结果上来看, 导致了t1的写丢失. 模式下会出现.
如果语句话就能将逻辑表达清楚的, 则不存在写丢失的问题.
update salary set pay = pay + 100 where id = 123;
因为, 在更新时已经将该行锁住.
而当使用外部逻辑(或者存储过程), 都会存在这样的问题.
Cursor Stability 就是为了解决写丢失.
Snapshot isolation
每个事务, 在自己的一个数据版本上操作, 因此读永远不会阻塞. 每个数据都有一个版本. 写的时候, 只有当所有的数据版本不变时, 才会写入, 否则提交失败. 第一个提交的人赢!
##
使用了共读锁, 但是需要的是独占锁. 即每个事务看起来不是一个一个顺序执行完成的. 需要读写锁.
粒度: READ-UNCOMMITTED < READ-COMMITTED < Cursor Stability < REPEATABLE-READ < SERIALIZABLE.
对于MySQL隔离级别的说明
在默认的 REPEATABLE-READ 模式下:
- 需要注意的是MySQL并不是严格的REPEATABLE-READ, 存在lost update问题; ???
- 实现了幻读的避免. ???
TODO: next key locking
事务的实现
不同级别事务, 通过不同粒度的锁来实现.
锁
- 共读锁 / 共享锁: 多个读者共享.
- 读写锁 / 互斥锁: 只能有一个写者.
也需要读写锁, 因为有可能在某一时刻, 从读锁转换为写锁. 在为转换成写锁前, 还是可以和其他读锁共存的.
写锁会阻塞读锁, 典型的 Reader / Writer 问题.
悲观锁 乐观锁 的概念
乐观锁的基本思路: CAS
MVCC (multi-version concurrency control)
通过多个版本数据并存, 每个事务操作一个版本的数据, 从而提高并发. 在合并的时候解决冲突.
类比版本控制git的执行方式, 每个分支上开发, 这样大家单独开发, 提高了效率. 相当于如果没有监测到冲突就自动合并. 不过严格对比起来, 应当说每个开发这在本次提交里面看到的文件, 其他提交者都没修改.
前提是关注点不一样, 冲突很少的情况下才可以.
实现
PostgreSQL: 存每个版本的数据. InnoDb: 只保留最新版本数据, 但是旧版本数据可以通过日志回溯得到. 所以导致的一个问题就是, 如果一个事务过长的话, 会导致undo log 太多 ???
[mvcc-survey]
加锁
根据条件加锁, 如果有索引可以对索引加锁, 否则需要对全表加锁 ???
预先加锁
select … from … where …
对索引的启示
查询条件是否使用索引影响到是行锁还是表锁.
参考
- A Critique of ANSI SQL Isolation Levels
- http://dev.mysql.com/doc/refman/5.7/en/set-transaction.html
- http://stackoverflow.com/questions/10040785/mysql-repeatable-read-and-lost-update-phantom-reads
- https://blogs.oracle.com/mysqlinnodb/entry/repeatable_read_isolation_level_in
- http://coolshell.cn/articles/6790.html
- http://tech.meituan.com/innodb-lock.html
- http://blog.csdn.net/beiigang/article/details/43226403