本项目聚焦高并发的基础知识,至底向上得研究:

github 仓库 (分支 master / isolation / propagation / multithreading)

理念:
Spring Boot 和基于Groovy的测试框架Spock编写测试用例。master 分支 isolation 分支propagation 分支 multithreading 分支SQL92标准 规定了四种隔离级别,与具体的数据库底层实现无关。如果数据库引擎能完全规避掉脏读,那么它对外就可以声明自己为RC的隔离级别。MySQL 的Innodb引擎,若使用Serializable,底层用两阶段加锁,会把所有读都隐式添加上lock in share mode,两阶段加锁会造成大量死锁。MySQL 采用 RR 时,底层用next-key 解决了幻读问题。换言之, MySQL 的 RR 是逼近Serializable的能力,又压低了死锁的概率,能应对大部分应用场景。
禁用了gap锁,减少了锁的作用范围,即减少了死锁风险
使用了"半一致读"降低了锁等待,即减少了死锁风险,也提高了读效率
官方原文:https://dev.mysql.com/doc/refman/5.7/en/glossary.html
semi-consistent read
A type of read operation used for UPDATE statements, that is a combination of READ COMMITTED and consistent read. When an UPDATE statement examines a row that is already locked, InnoDB returns the latest committed version to MySQL so that MySQL can determine whether the row matches the WHERE condition of the UPDATE. If the row matches (must be updated), MySQL reads the row again, and this time InnoDB either locks it or waits for a lock on it. This type of read operation can only happen when the transaction has the READ COMMITTED isolation level, or when the innodb_locks_unsafe_for_binlog option is enabled. innodb_locks_unsafe_for_binlog was removed in MySQL 8.0.
大致意思是,"semi-consistent read"只出现在update语句中。区别于其他隔离级别,update是加排他锁,RC中的"semi-consistent read"在遇到锁的时候会先去按序读一下where命中条件是否冲突,不冲突则可以更新。这个阐述其实有点矛盾,为什么即“锁等待”又“不冲突”?网上有篇很不错的文章可以了解
lock in share mode ,但放大了死锁隐患NonRepeatableReadFixByLockSpecDeadLockSpec 体现了同样条件下,使用lock in share mode 更容易造成死锁。 按下面顺序加锁会导致死锁
A事务 lock in share mode =》 获取读锁成功
B事务 lock in share mode =》 获取读锁成功
B事务 update 期望获得锁升级,尝试获取写锁,被A事务的读锁阻塞。 =》 等待A读锁释放
A事务 update 期望获得锁升级,尝试获取写锁,被B事务的读锁阻塞。 =》 等待B读锁释放
整理一下上述顺序:
A事务持有读锁不释放,等待B事务释放读锁
B事务持有读锁不释放,等待A事务释放读锁
死锁发生。
for update 加锁NonRepeatableReadFixByLockSpeclock in share mode , 排他锁牺牲了并发度,提升了一致性。PhantomRowSpec-- id 序列: 1 2 3 5
-- 事务A
update `bank_account` set `balance` = `balance` + 1 where id > 2; //更新两行 只锁住了 id = 3 和 id = 5 的行
-- 事务B
-- 插入id = 4 的记录成功
INSERT INTO `bank_account` (id, balance) valuse (4, 5);
-- 事务A
update `bank_account` set `balance` = `balance` - 1 where id > 2; // 同样的where条件却更新了三行, 造成幻读
摘自《数据密集型应用系统设计》
-- 业务逻辑:保证有两名医生目前在值班,则自己可以请假
-- 1234值班表中正在值班的医生人数
-- 事务A
select count(*) as currentlyOncall from doctor where on_call = true and shift_id = 1234;
-- 事务B
select count(*) as currentlyOncall from doctor where on_call = true and shift_id = 1234;
-- 事务A
if (currentlyOncall >= 2) {
update doctoer set on_call = false where name = 'Alice' and and shift_id = 1234;
}
-- 事务B
if (currentlyOncall >= 2) {
update doctoer set on_call = false where name = 'Bob' and and shift_id = 1234;
}
-- Alice 和 Bob 都觉得自己请假不会影响到值班,事实是他们互相都认为对方不会请假,所以都请假了,导致无人值班。
select `balance` as currentBalance from `bank_account` where id = 1;
update `balance` set `balance` = currentBalance + 1 where id = 1;
update `balance` set `balance` = `balance` + 1 where id = 1;
select `balance` as currentBalance from `bank_account` where id = 1 for update;
update `balance` set `balance` = currentBalance + 1 where id = 1;
for update 锁解决,那么缺少这种行数据怎么办呢? 如:同一个会议室不能在同一个整点被预定,这时候预定记录是不存在的for update 加在这个表的记录上,起到互斥的作用。对于MySQL而言,RR也无法做到解决所有隐患。而使用Serializable又大大增加了死锁几率。如果既能获得RR的快照隔离及Serializable的一致性保证那就太好了,有幸的是,Serializable Snapshot Isolation (SSI) 被提出。
PostgreSQL9.1 后使用了 SSI。相比于其他并发控制机制,SSI尚需在实践中证明其性能。即使如此,它很有可能成为未来数据库的标配 —— 《数据密集型应用系统设计》MySQL 5.7 Manual
《数据密集型应用系统设计》