介绍
在关于ACID和数据库事务的文章中,我介绍了SQL标准描述的三种现象:
脏读
不可重复读
幻读
尽管这些可以很好地区分四个隔离级别(未提交读,已提交读,可重复读和可序列化),但实际上,还有更多的现象需要考虑。 1995年的论文(《 ANSI SQL隔离级别批判》)介绍了标准规范中省略的其他现象。
在我的《高性能Java持久性》一书中,我决定坚持“事务”一章,因为它对数据访问的有效性和效率都非常重要。
领域模型
对于以下示例,我将使用以下两个实体:
在我们的虚构应用程序中,更改“ 帖子”标题后,必须将作者记录在关联的“ PostDetails”记录中。 如果未防止读写时滞,则可以破坏此域模型约束,如以下测试案例所示。
读取歪斜
以下测试模拟了读取偏斜如何发生:
doInConnection(aliceConnection -> {
prepareConnection(aliceConnection);
String title = selectStringColumn(
aliceConnection,
selectPostTitleSql()
);
executeSync(() -> {
doInConnection(bobConnection -> {
prepareConnection(bobConnection);
try {
update(
bobConnection,
updatePostTitleParamSql(),
new Object[]{"Bob"}
);
update(
bobConnection,
updatePostDetailsAuthorParamSql(),
new Object[]{"Bob"}
);
} catch (Exception e) {
LOGGER.info("Exception thrown", e);
preventedByLocking.set(true);
}
});
});
String createdBy = selectStringColumn(
aliceConnection,
selectPostDetailsAuthorSql()
);
});
爱丽丝选择一个帖子标题
Bob潜入并更新了Post和PostDetails实体
Alice线程将继续,并且她选择PostDetails记录
如果允许读取偏斜,则Alice会看到Bob的更新,并且可以假定先前的Post版本(在交易开始时读取)是Bob发行的(可能不准确)。
在四个最常见的关系数据库系统上运行此测试可获得以下结果:
数据库隔离级别
读取歪斜
Oracle读已提交
是
Oracle可序列化
没有
SQL Server读未提交
是
SQL Server读取已提交
是
SQL Server读取提交的快照隔离
是
SQL Server可重复读取
没有
SQL Server可序列化
没有
SQL Server快照隔离
没有
PostgreSQL读未提交
是
PostgreSQL读已提交
是
PostgreSQL可重复读
没有
PostgreSQL可序列化
没有
MySQL读取未提交
是
MySQL读取提交
是
MySQL可重复读
没有
MySQL可序列化
没有
写偏斜
要模拟写偏斜,您需要执行以下测试用例:
doInConnection(aliceConnection -> {
prepareConnection(aliceConnection);
String title = selectStringColumn(
aliceConnection,
selectPostTitleSql()
);
String createdBy = selectStringColumn(
aliceConnection,
selectPostDetailsAuthorSql()
);
executeSync(() -> {
doInConnection(bobConnection -> {
prepareConnection(bobConnection);
try {
String bobTitle = selectStringColumn(
bobConnection,
selectPostTitleSql()
);
String bonCreatedBy = selectStringColumn(
bobConnection,
selectPostDetailsAuthorSql()
);
update(
bobConnection,
updatePostTitleParamSql(),
new Object[]{"Bob"}
);
} catch (Exception e) {
LOGGER.info("Exception thrown", e);
preventedByLocking.set(true);
}
});
});
update(
aliceConnection,
updatePostDetailsAuthorParamSql(),
new Object[]{"Alice"}
);
});
爱丽丝从PostDetails记录中选择Post标题和作者
Bob还选择了Post标题和相关的作者,但他决定仅更新标题
爱丽丝想在不更改帖子标题的情况下更新PostDetails记录
如果允许写偏斜,则将执行Alice和Bob不相交的写操作,而不会受到控制这两个记录的约束的阻止。
在四个最常见的关系数据库系统上运行此测试可获得以下结果:
数据库隔离级别
写偏斜
Oracle读已提交
是
Oracle可序列化
是
SQL Server读未提交
是
SQL Server读取已提交
是
SQL Server读取提交的快照隔离
是
SQL Server可重复读取
没有
SQL Server可序列化
没有
SQL Server快照隔离
是
PostgreSQL读未提交
是
PostgreSQL读已提交
是
PostgreSQL可重复读
是
PostgreSQL可序列化
没有
MySQL读取未提交
是
MySQL读取提交
是
MySQL可重复读
是
MySQL可序列化
没有
写偏斜在多版本并发控制机制中很普遍,即使声称使用“可序列化”,Oracle也无法阻止它,而实际上它只是一个快照隔离级别。
当使用“可重复读”和“可序列化”时,SQL Server默认的基于锁定的隔离级别可以防止写偏斜。 快照隔离级别(基于MVCC)中的任何一个都不能阻止/检测到它。
PostgreSQL使用更高级的可序列化快照隔离级别来阻止它
MySQL在使用Serializable时会使用共享锁,因此即使InnoDB也是基于MVCC的,也可以防止写偏斜
如果您对此主题感兴趣,那么您也可以阅读我正在编写的《 高性能Java持久性》一书。
翻译自: https://www.javacodegeeks.com/2015/10/a-beginners-guide-to-read-and-write-skew-phenomena.html