休眠锁定模式– PESSIMISTIC_READ和PESSIMISTIC_WRITE如何工作
介紹
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 ConnectionBob的排他鎖請求等待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 ConnectionBob的共享鎖請求等待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 ConnectionBob的排他鎖請求必須等待Alice釋放其鎖。
結論
關系數據庫系統使用鎖來保留ACID保證 ,因此了解共享和獨占行級鎖如何互操作非常重要。 顯式悲觀鎖是一種非常強大的數據庫并發控制機制,您甚至可以使用它來修復樂觀鎖競爭條件 。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2015/02/hibernate-locking-patterns-how-does-pessimistic_read-and-pessimistic_write-work.html
總結
以上是生活随笔為你收集整理的休眠锁定模式– PESSIMISTIC_READ和PESSIMISTIC_WRITE如何工作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (wait linux)
- 下一篇: 华为watchgt3pro能不能测血压(