介绍
Java Persistence API带有完善的并发控制机制,支持隐式和显式锁定。 隐式锁定机制很简单,它依赖于:
- 乐观锁定:实体状态更改可以触发版本增加
- 行级锁定:基于当前运行的事务隔离级别 ,INSERT / UPDATE / DELETE语句可能会获取排他行锁
虽然隐式锁定适用于许多情况,但显式锁定机制可以利用更细粒度的并发控制。
在以前的文章中,我介绍了显式的乐观锁定模式:
- 乐观的
- OPTIMISTIC_FORCE_INCREMENT
- PESSIMISTIC_FORCE_INCREMENT
在这篇文章中,我将解开显式的悲观锁模式:
- PESSIMISTIC_READ
- PESSIMISTIC_WRITE
读写器锁
数据库系统是高度并发的环境,因此许多并发理论习惯用法也适用于数据库访问。 必须将并发更改序列化以保留数据完整性,因此,即使通常通过Multiversion并发控制机制对其进行补充,大多数数据库系统仍使用两阶段锁定策略。
因为互斥锁定会阻碍可伸缩性(平等地处理读写),所以大多数数据库系统都使用读写器锁定同步方案,因此:
- 共享(读取)锁会阻止作者,从而允许多个读者继续
- 排他(写入)锁同时阻止读取器和写入器,从而使所有写入操作都按顺序应用
因为锁定语法不是SQL标准的一部分,所以每个RDBMS都选择了不同的语法:
数据库名称 | 共享锁语句 | 排他锁声明 |
---|---|---|
甲骨文 | 更新 | 更新 |
的MySQL | 锁定共享模式 | 更新 |
Microsoft SQL服务器 | 带(HOLDLOCK,ROWLOCK) | 带(上锁,上锁) |
PostgreSQL的 | 分享 | 更新 |
DB2 | 只供RS阅读 | 用于RS更新 |
Java持久性抽象层隐藏了特定于数据库的锁定语义,提供了仅需要两个锁定模式的通用API。 使用PESSIMISTIC_READ锁定模式类型获取共享/读取锁定,而使用PESSIMISTIC_WRITE请求排他/写入锁定。
PostgreSQL行级锁定模式
对于下一个测试用例,我们将使用PostgreSQL,因为它既支持独占锁定 ,也支持共享显式锁定 。
以下所有测试将使用相同的并发实用程序,模拟两个用户:Alice和Bob。 每个测试方案将验证特定的读/写锁定组合。
private void testPessimisticLocking(ProductLockRequestCallable primaryLockRequestCallable, ProductLockRequestCallable secondaryLockRequestCallable) {doInTransaction(session -> {try {Product product = (Product) session.get(Product.class, 1L);primaryLockRequestCallable.lock(session, product);executeAsync(() -> {doInTransaction(_session -> {Product _product = (Product) _session.get(Product.class, 1L);secondaryLockRequestCallable.lock(_session, _product);});},endLatch::countDown);sleep(WAIT_MILLIS);} catch (StaleObjectStateException e) {LOGGER.info("Optimistic locking failure: ", e);}});awaitOnLatch(endLatch);
}
情况1:PESSIMISTIC_READ不阻止PESSIMISTIC_READ锁定请求
第一个测试将检查两个并发的PESSIMISTIC_READ锁定请求如何交互:
@Test
public void testPessimisticReadDoesNotBlockPessimisticRead() throws InterruptedException {LOGGER.info("Test PESSIMISTIC_READ doesn't block PESSIMISTIC_READ");testPessimisticLocking((session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_READ)).lock(product);LOGGER.info("PESSIMISTIC_READ acquired");},(session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_READ)).lock(product);LOGGER.info("PESSIMISTIC_READ acquired");});
}
运行此测试,我们得到以下输出:
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Test PESSIMISTIC_READ doesn't block PESSIMISTIC_READ#Alice selects the Product entity
[Alice]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]} #Alice acquires a SHARED lock on the Product entity
[Alice]: Time:1 Query:{[
SELECT id
FROM product
WHERE id =?
AND version =? FOR share
][1,0]}
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_READ acquired#Alice waits for 500ms
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Wait 500 ms!#Bob selects the Product entity
[Bob]: Time:1 Query:{[SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]}#Bob acquires a SHARED lock on the Product entity
[Bob]: Time:1 Query:{[
SELECT id
FROM product
WHERE id =?
AND version =? FOR share
][1,0]}
[Bob]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_READ acquired#Bob's transactions is committed
[Bob]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection#Alice's transactions is committed
[Alice]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection
在这种情况下,没有任何争用。 爱丽丝和鲍勃都可以获取共享锁,而不会遇到任何冲突。
情况2:PESSIMISTIC_READ阻止UPDATE隐式锁定请求
第二种情况将演示共享锁如何防止并发修改。 爱丽丝将获得一个共享锁,而鲍勃将尝试修改该锁定的实体:
@Test
public void testPessimisticReadBlocksUpdate() throws InterruptedException {LOGGER.info("Test PESSIMISTIC_READ blocks UPDATE");testPessimisticLocking((session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_READ)).lock(product);LOGGER.info("PESSIMISTIC_READ acquired");},(session, product) -> {product.setDescription("USB Flash Memory Stick");session.flush();LOGGER.info("Implicit lock acquired");});
}
测试生成以下输出:
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Test PESSIMISTIC_READ blocks UPDATE#Alice selects the Product entity
[Alice]: Time:0 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]} #Alice acquires a SHARED lock on the Product entity
[Alice]: Time:0 Query:{[
SELECT id
FROM product
WHERE id =?
AND version =? FOR share
][1,0]}
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_READ acquired#Alice waits for 500ms
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Wait 500 ms!#Bob selects the Product entity
[Bob]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]} #Alice's transactions is committed
[Alice]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection#Bob can acquire the Product entity lock, only after Alice's transaction is committed
[Bob]: Time:427 Query:{[
UPDATE product
SET description = ?,price = ?,version = ?
WHERE id = ?AND version = ?
][USB Flash Memory Stick,12.99,1,1,0]}
[Bob]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Implicit lock acquired#Bob's transactions is committed
[Bob]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection
尽管Bob可以选择Product实体,但UPDATE会一直延迟到提交Alice事务为止(这就是UPDATE花费427ms运行的原因)。
情况3:PESSIMISTIC_READ阻止PESSIMISTIC_WRITE锁定请求
辅助PESSIMISTIC_WRITE锁定请求也表现出相同的行为:
@Test
public void testPessimisticReadBlocksPessimisticWrite() throws InterruptedException {LOGGER.info("Test PESSIMISTIC_READ blocks PESSIMISTIC_WRITE");testPessimisticLocking((session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_READ)).lock(product);LOGGER.info("PESSIMISTIC_READ acquired");},(session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(product);LOGGER.info("PESSIMISTIC_WRITE acquired");});
}
提供以下输出:
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Test PESSIMISTIC_READ blocks PESSIMISTIC_WRITE#Alice selects the Product entity
[Alice]: Time:0 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]}#Alice acquires a SHARED lock on the Product entity
[Alice]: Time:1 Query:{[
SELECT id
FROM product
WHERE id =?
AND version =? FOR share
][1,0]}
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_READ acquired#Alice waits for 500ms
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Wait 500 ms!#Bob selects the Product entity
[Bob]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]} #Alice's transactions is committed
[Alice]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection#Bob can acquire the Product entity lock, only after Alice's transaction is committed
[Bob]: Time:428 Query:{[
SELECT id
FROM product
WHERE id = ?AND version = ?
FOR UPDATE
][1,0]}
[Bob]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_WRITE acquired#Bob's transactions is committed
[Bob]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection
Bob的排他锁请求等待Alice的共享锁被释放。
情况4:PESSIMISTIC_READ阻止PESSIMISTIC_WRITE锁定请求,NO WAIT快速失败
Hibernate提供了一个PESSIMISTIC_NO_WAIT超时指令,该指令转换为特定于数据库的NO_WAIT锁获取策略。
PostgreSQL NO WAIT指令描述如下:
为防止该操作等待其他事务提交,请使用NOWAIT选项。 使用NOWAIT,如果无法立即锁定选定的行,该语句将报告错误,而不是等待。 注意,NOWAIT仅适用于行级锁-所需的ROW SHARE表级锁仍以常规方式获取(请参见第13章)。 如果需要不等待就获取表级锁,则可以先将LOCK与NOWAIT选项一起使用。
@Test
public void testPessimisticReadWithPessimisticWriteNoWait() throws InterruptedException {LOGGER.info("Test PESSIMISTIC_READ blocks PESSIMISTIC_WRITE, NO WAIT fails fast");testPessimisticLocking((session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_READ)).lock(product);LOGGER.info("PESSIMISTIC_READ acquired");},(session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).setTimeOut(Session.LockRequest.PESSIMISTIC_NO_WAIT).lock(product);LOGGER.info("PESSIMISTIC_WRITE acquired");});
}
该测试生成以下输出:
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Test PESSIMISTIC_READ blocks PESSIMISTIC_WRITE, NO WAIT fails fast#Alice selects the Product entity
[Alice]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]}#Alice acquires a SHARED lock on the Product entity
[Alice]: Time:1 Query:{[
SELECT id
FROM product
WHERE id =?
AND version =? FOR share
][1,0]}
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_READ acquired#Alice waits for 500ms
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Wait 500 ms!#Bob selects the Product entity
[Bob]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]}#Bob tries to acquire an EXCLUSIVE lock on the Product entity and fails because of the NO WAIT policy
[Bob]: Time:0 Query:{[
SELECT id
FROM product
WHERE id = ?AND version = ?
FOR UPDATE nowait
][1,0]}
[Bob]: o.h.e.j.s.SqlExceptionHelper - SQL Error: 0, SQLState: 55P03
[Bob]: o.h.e.j.s.SqlExceptionHelper - ERROR: could not obtain lock on row in relation "product"#Bob's transactions is rolled back
[Bob]: o.h.e.t.i.j.JdbcTransaction - rolled JDBC Connection#Alice's transactions is committed
[Alice]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection
由于Alice已经在与产品实体相关联的数据库行上持有共享锁,因此Bob的排他锁请求立即失败。
情况5:PESSIMISTIC_WRITE阻止PESSIMISTIC_READ锁定请求
下一个测试证明排他锁将始终阻止共享锁获取尝试:
@Test
public void testPessimisticWriteBlocksPessimisticRead() throws InterruptedException {LOGGER.info("Test PESSIMISTIC_WRITE blocks PESSIMISTIC_READ");testPessimisticLocking((session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(product);LOGGER.info("PESSIMISTIC_WRITE acquired");},(session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_READ)).lock(product);LOGGER.info("PESSIMISTIC_WRITE acquired");});
}
生成以下输出:
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Test PESSIMISTIC_WRITE blocks PESSIMISTIC_READ#Alice selects the Product entity
[Alice]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]} #Alice acquires an EXCLUSIVE lock on the Product entity
[Alice]: Time:0 Query:{[
SELECT id
FROM product
WHERE id = ?AND version = ?
FOR UPDATE
][1,0]}
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_WRITE acquired#Alice waits for 500ms
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Wait 500 ms!#Bob selects the Product entity
[Bob]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]}#Alice's transactions is committed
[Alice]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection#Bob can acquire the Product entity SHARED lock, only after Alice's transaction is committed
[Bob]: Time:428 Query:{[
SELECT id
FROM product
WHERE id =?
AND version =? FOR share
][1,0]}
[Bob]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_WRITE acquired#Bob's transactions is committed
[Bob]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection
Bob的共享锁请求等待Alice的事务结束,以便释放所有获得的锁。
情况6:PESSIMISTIC_WRITE阻止PESSIMISTIC_WRITE锁定请求
排他锁也阻止排他锁:
@Test
public void testPessimisticWriteBlocksPessimisticWrite() throws InterruptedException {LOGGER.info("Test PESSIMISTIC_WRITE blocks PESSIMISTIC_WRITE");testPessimisticLocking((session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(product);LOGGER.info("PESSIMISTIC_WRITE acquired");},(session, product) -> {session.buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE)).lock(product);LOGGER.info("PESSIMISTIC_WRITE acquired");});
}
测试生成以下输出:
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Test PESSIMISTIC_WRITE blocks PESSIMISTIC_WRITE#Alice selects the Product entity
[Alice]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]} #Alice acquires an EXCLUSIVE lock on the Product entity
[Alice]: Time:0 Query:{[
SELECT id
FROM product
WHERE id = ?AND version = ?
FOR UPDATE
][1,0]}
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_WRITE acquired#Alice waits for 500ms
[Alice]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - Wait 500 ms!#Bob selects the Product entity
[Bob]: Time:1 Query:{[
SELECT lockmodepe0_.id AS id1_0_0_,lockmodepe0_.description AS descript2_0_0_,lockmodepe0_.price AS price3_0_0_,lockmodepe0_.version AS version4_0_0_
FROM product lockmodepe0_
WHERE lockmodepe0_.id = ?
][1]}#Alice's transactions is committed
[Alice]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection#Bob can acquire the Product entity SHARED lock, only after Alice's transaction is committed
[Bob]: Time:428 Query:{[
SELECT id
FROM product
WHERE id =?
AND version =? FOR update
][1,0]}
[Bob]: c.v.h.m.l.c.LockModePessimisticReadWriteIntegrationTest - PESSIMISTIC_WRITE acquired#Bob's transactions is committed
[Bob]: o.h.e.t.i.j.JdbcTransaction - committed JDBC Connection
Bob的排他锁请求必须等待Alice释放其锁。
结论
关系数据库系统使用锁来保留ACID保证 ,因此了解共享和独占行级锁如何互操作非常重要。 显式悲观锁是一种非常强大的数据库并发控制机制,您甚至可以使用它来修复乐观锁竞争条件 。
- 代码可在GitHub上获得 。
翻译自: https://www.javacodegeeks.com/2015/02/hibernate-locking-patterns-how-does-pessimistic_read-and-pessimistic_write-work.html