Spring Boot2+JPA之悲觀鎖和樂觀鎖實戰
前言
大量的請求,或者同時的操作,容易導致系統在業務上發生併發的問題. 通常講到併發,解決方案無非就是前端限制重複提交,後臺進行悲觀鎖或者樂觀鎖限制.
悲觀鎖與併發
悲觀鎖(Pessimistic Lock)
,顧名思義,就是很悲觀,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖
,這樣別人想拿這個資料就會block直到解鎖,可以理解為獨佔鎖
。在java中synchronized
和ReentrantLock
重入鎖等鎖就是悲觀鎖,資料庫中表鎖、行鎖、讀寫鎖等也是悲觀鎖。
利用SQL的for update解決併發問題
行鎖就是運算元據的時候把這一行資料鎖住,其他執行緒想要讀寫必須等待,但同一個表的其他資料還是能被其他執行緒操作的。只要在需要查詢的sql後面加上for update
,就能鎖住查詢的行,特別要注意查詢條件必須要是索引列
,如果不是索引就會變成表鎖,把整個表都鎖住。
public interface ArticleRepository extends JpaRepository<Article, Long> {
@Query(value = "select * from article a where a.id = :id for update", nativeQuery = true)
Optional<Article> findArticleForUpdate(Long id);
}
利用JPA的@Lock行鎖註解解決併發問題
如果說for update
的做法太原始,那麼JPA有提供一個更加優雅的方法,就是@Lock註解 .
為Repository新增JPA的鎖方法,其中LockModeType.PESSIMISTIC_WRITE
引數就是行鎖。
關於LockModeType
這個型別,可以在這找到文件 https://docs.oracle.com/javaee/7/api/javax/persistence/LockModeType.html
- NONE: No lock.
- OPTIMISTIC: Optimistic lock.
- OPTIMISTIC_FORCE_INCREMENT: Optimistic lock, with version update.
- PESSIMISTIC_FORCE_INCREMENT: Pessimistic write lock, with version update.
- PESSIMISTIC_READ: Pessimistic read lock.
- PESSIMISTIC_WRITE: Pessimistic write lock.
- READ: Synonymous with OPTIMISTIC.
- WRITE: Synonymous with OPTIMISTIC_FORCE_INCREMENT.
public interface ArticleRepository extends JpaRepository<Article, Long> {
@Lock(value = LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Article a where a.id = :id")
Optional<Article> findArticleWithPessimisticLock(Long id);
}
如果是@NameQuery
,則可以
@NamedQuery(name="lockArticle",query="select a from Article a where a.id = :id",lockMode = PESSIMISTIC_READ)
public class Article
如果用entityManager
的方式,則可以設定LocakMode
:
Query query = entityManager.createQuery("from Article where articleId = :id");
query.setParameter("id", id);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
query.getResultList();
樂觀鎖與併發
樂觀鎖(Optimistic Lock)
,顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在提交更新的時候會判斷一下在此期間別人有沒有去修改。所以悲觀鎖是限制其他執行緒
,而樂觀鎖是限制自己
,雖然他的名字有鎖,但是實際上不算上鎖,通常為version版本號
機制,還有CAS演算法 .
利用version欄位解決併發問題
版本號機制就是在資料庫中加一個欄位version
當作版本號。那麼獲取Article的時候就會帶一個版本號,比如version=1,然後你對這個Article一波操作,操作完之後要插入到資料庫了。校驗一下version版本號,發現在資料庫裡對應Article記錄的version=2,這和我手裡的版本不一樣啊,說明提交的Article不是最新的,那麼就不能update到資料庫了,進行報錯把,這樣就避免了併發時資料衝突的問題。
public interface ArticleRepository extends JpaRepository<Article, Long> {
@Modifying
@Query(value = "update article set content= :content, version = version + 1 where id = :id and version = :version", nativeQuery = true)
int updateArticleWithVersion(Long id, String content, Long version);
}
public void postComment(Long articleId, String content) {
//get article
Optional<Article> articleOptional = articleRepository.findById(articleId);
//update with Optimistic Lock
int count = articleRepository.updateArticleWithVersion(article.getId(), content, article.getVersion());
if (count == 0) {
throw new RuntimeException("更新資料失敗,請重新整理重試");
}else{
articleRepository.save(article);
}
}
利用JPA的@Version版本機制解決併發問題
有沒有更優雅的方式? 當然,必須有,那就是JPA自帶的@Version
方式實現樂觀鎖。
- each entity class must have only one version attribute .每個實體類
只能有一個@Version欄位
,不能多 - it must be placed in the primary table for an entity mapped to several tables . 對於對映到多個表的實體,必須將其放置在
主表
中 - type of a version attribute must be one of the following: int, Integer, long, Long, short, Short, java.sql.Timestamp ,
@Version支援的型別必須是以下型別
:
- int
- Integer
- long
- Long
- short
- Short
- java.sql.Timestamp
首先在Article實體類的version欄位上加上@Version註解
@Data
@Entity
public class Article{
@Id
private Long id;
//......
@Version
private Integer version;
}
Article article = entityManager.find(Article.class, id);
entityManager.lock(article , LockModeType.OPTIMISTIC);
entityManager.refresh(article , LockModeType.READ);
什麼時候用悲觀鎖或者樂觀鎖
悲觀鎖
適合寫多讀少
的場景。因為在使用的時候該執行緒會獨佔這個資源,就適合用悲觀鎖,否則使用者只是瀏覽文章的話,用悲觀鎖就會經常加鎖,增加了加鎖解鎖的資源消耗。
樂觀鎖
適合寫少讀多
的場景。由於樂觀鎖在發生衝突的時候會回滾或者重試,如果寫的請求量很大的話,就經常發生衝突,結合事務會有經常的回滾和重試,這樣對系統資源消耗也是非常大。
所以悲觀鎖和樂觀鎖沒有絕對的好壞,必須結合具體的業務情況來決定使用哪一種方式。另外在阿里巴巴開發手冊裡也有提到:
如果每次訪問衝突概率小於
20%
,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於3
次。
阿里巴巴建議以衝突概率20%
這個數值作為分界線來決定使用樂觀鎖和悲觀鎖,雖然說這個數值不是絕對的,但是作為阿里巴巴各個大佬總結出來的也是一個很好的參考。
相關文章
- 悲觀鎖和樂觀鎖
- laravel樂觀鎖和悲觀鎖Laravel
- 理解樂觀鎖和悲觀鎖
- MySQL樂觀鎖和悲觀鎖介紹MySql
- Java中的鎖之樂觀鎖與悲觀鎖Java
- mysql悲觀鎖以樂觀鎖MySql
- 面試必備之悲觀鎖與樂觀鎖面試
- 面試必備之樂觀鎖與悲觀鎖面試
- Java 中的悲觀鎖和樂觀鎖的實現Java
- MySQL鎖(樂觀鎖、悲觀鎖、多粒度鎖)MySql
- Redis的事務、樂觀鎖和悲觀鎖Redis
- SQLServer樂觀鎖定和悲觀鎖定例項SQLServer
- java-樂觀鎖與悲觀鎖Java
- MybatisPlus - [03] 樂觀鎖&悲觀鎖MyBatis
- 樂觀鎖和悲觀鎖策略的區別與實現
- 利用MySQL中的樂觀鎖和悲觀鎖實現分散式鎖MySql分散式
- 資料庫中的悲觀鎖和樂觀鎖資料庫
- JPA和Hibernate的樂觀鎖與悲觀鎖
- 經典問題之樂觀鎖和悲觀鎖及使用場景
- MySQL 悲觀鎖與樂觀鎖的詳解MySql
- Java彌散系列 - 樂觀鎖與悲觀鎖Java
- 小議“悲觀鎖和樂觀鎖”的原理、場景、示例
- SQL SERVER樂觀鎖定和悲觀鎖定使用例項SQLServer
- [轉帖]SQL Server 鎖機制 悲觀鎖 樂觀鎖 實測解析SQLServer
- 悲觀鎖與樂觀鎖的實現(詳情圖解)圖解
- 關於樂觀鎖與悲觀鎖的實際應用
- SSM (十五) 樂觀鎖與悲觀鎖的實際應用SSM
- 【鎖機制】共享鎖、排它鎖、悲觀鎖、樂觀鎖、死鎖等等
- 樂觀鎖與悲觀鎖及應用舉例
- 樂觀鎖和悲觀鎖在kubernetes中的應用
- 面試必備之樂觀鎖與悲觀鎖(程式設計師必看)面試程式設計師
- Java併發程式設計(05):悲觀鎖和樂觀鎖機制Java程式設計
- 【每日鮮蘑】從資料庫看樂觀鎖、悲觀鎖資料庫
- 面試必備的資料庫悲觀鎖與樂觀鎖面試資料庫
- 關於庫存超賣問題,悲觀鎖和樂觀鎖的不同實現
- 解鎖你的資料庫:JPA和Hibernate的樂觀鎖與悲觀鎖資料庫
- Java鎖最全詳解:樂觀鎖/悲觀鎖+公平鎖/非公平鎖+獨享鎖/共享鎖Java
- 面試必備知識點:悲觀鎖和樂觀鎖的那些事兒面試