专栏名称: java那些事
分享java开发中常用的技术,分享软件开发中各种新技术的应用方法。每天推送java技术相关或者互联网相关文章。关注“java那些事”,让自己做一个潮流的java技术人!《java程序员由笨鸟到菜鸟》系列文章火热更新中。
目录
相关文章推荐
芋道源码  ·  MySQL数据实时同步到Elasticsea ... ·  昨天  
芋道源码  ·  如何快速同步第三方平台数据? ·  4 天前  
芋道源码  ·  几个必会的JDK性能监控和故障处理工具 ·  4 天前  
芋道源码  ·  DeepSeek 全面指南:95% ... ·  4 天前  
51好读  ›  专栏  ›  java那些事

解决并发问题,数据库常用的两把锁!

java那些事  · 公众号  · Java  · 2019-04-03 16:00

正文

作者:黄青石

来源:cnblogs.com/huangqingshi/p/10165409.html


在写入数据库的时候需要有锁,比如同时写入数据库的时候会出现丢数据,那么就需要锁机制。

数据锁分为乐观锁和悲观锁

它们使用的场景如下:

  • 乐观锁适用于写少读多的情景,因为这种乐观锁相当于JAVA的CAS,所以多条数据同时过来的时候,不用等待,可以立即进行返回。

  • 悲观锁适用于写多读少的情景,这种情况也相当于JAVA的 synchronized ,reentrantLock等,大量数据过来的时候,只有一条数据可以被写入,其他的数据需要等待。执行完成后下一条数据可以继续。

他们实现的方式上有所不同。

乐观锁采用版本号的方式,即当前版本号如果对应上了就可以写入数据,如果判断当前版本号不一致,那么就不会更新成功,比如


  1. update table set column = value

  2. where version=${version} and otherKey = ${otherKey}


悲观锁实现的机制一般是在执行更新语句的时候采用for update方式,比如


  1. update table set column= 'value' for update


这种情况where条件呢一定要涉及到数据库对应的索引字段,这样才会是行级锁,否则会是表锁,这样执行速度会变慢。

下面我就弄一个spring boot(springboot 2.1.1 + mysql + lombok + aop + jpa)工程,然后逐渐的实现乐观锁和悲观锁。 并发控制--悲观锁和乐观锁详解

假设有一个场景,有一个catalog商品目录表,然后还有一个browse浏览表,假如一个商品被浏览了,那么就需要记录下浏览的user是谁,并且记录访问的总数。

表的结构非常简单:


  1. create table catalog  (

  2. id int ( 11 ) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键' ,

  3. name varchar( 50 ) NOT NULL DEFAULT '' COMMENT '商品名称' ,

  4. browse_count int ( 11 ) NOT NULL DEFAULT 0 COMMENT '浏览数' ,

  5. version int ( 11 ) NOT NULL DEFAULT 0 COMMENT '乐观锁,版本号' ,

  6. PRIMARY KEY(id)

  7. ) ENGINE=INNODB DEFAULT CHARSET=utf8;


  8. CREATE table browse (

  9. id int ( 11 ) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键' ,

  10. cata_id int ( 11 ) NOT NULL COMMENT '商品ID' ,

  11. user varchar( 50 ) NOT NULL DEFAULT '' COMMENT '' ,

  12. create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间' ,

  13. PRIMARY KEY(id)

  14. ) ENGINE=INNODB DEFAULT CHARSET=utf8;


POM.XML的依赖如下:


  1. xml version= "1.0" encoding= "UTF-8" ?>

  2. xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"

  3. xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >

  4. 4.0.0

  5. org.springframework.boot

  6. spring-boot-starter-parent

  7. 2.1.1.RELEASE

  8. com.hqs

  9. dblock

  10. 1.0-SNAPSHOT

  11. dblock

  12. Demo project for Spring Boot


  13. 1.8


  14. org.springframework.boot

  15. spring-boot-starter-web


  16. org.springframework.boot

  17. spring-boot-devtools

  18. runtime

  19. mysql

  20. mysql-connector-java

  21. runtime

  22. org.springframework.boot

  23. spring-boot-starter-test

  24. test

  25. org.springframework.boot

  26. spring-boot-starter-data-jpa

  27. mysql

  28. mysql-connector-java

  29. org.projectlombok

  30. lombok

  31. true


  32. org.aspectj

  33. aspectjweaver

  34. 1.8.4



  35. org.springframework.boot

  36. spring-boot-maven-plugin



项目的结构如下:

介绍一下项目的结构的内容:

  • entity包: 实体类包。

  • repository包:数据库repository

  • service包: 提供服务的service

  • controller包: 控制器写入用于编写requestMapping。相关请求的入口类

  • annotation包: 自定义注解,用于重试。

  • aspect包: 用于对自定义注解进行切面。

  • DblockApplication: springboot的启动类。

  • DblockApplicationTests: 测试类。

咱们看一下核心代码的实现,参考如下,使用dataJpa非常方便,集成了CrudRepository就可以实现简单的CRUD,非常方便,有兴趣的同学可以自行研究。

实现乐观锁的方式有两种:

  1. 更新的时候将version字段传过来,然后更新的时候就可以进行version判断,如果version可以匹配上,那么就可以更新(方法:updateCatalogWithVersion)。

  2. 在实体类上的version字段上加入version,可以不用自己写SQL语句就可以它就可以自行的按照version匹配和更新,是不是很简单。


  1. public interface CatalogRepository extends CrudRepository < Catalog , Long > {


  2. @Query (value = "select * from Catalog a where a.id = :id for update" , nativeQuery = true )

  3. Optional < Catalog > findCatalogsForUpdate( @Param ( "id" ) Long id);


  4. @Lock (value = LockModeType .PESSIMISTIC_WRITE) //代表行级锁

  5. @Query ( "select a from Catalog a where a.id = :id" )

  6. Optional < Catalog > findCatalogWithPessimisticLock( @Param ( "id" ) Long id);


  7. @Modifying (clearAutomatically = true ) //修改时需要带上

  8. @Query (value = "update Catalog set browse_count = :browseCount, version = version + 1 where id = :id " +

  9. "and version = :version" , nativeQuery = true )

  10. int updateCatalogWithVersion( @Param ( "id" ) Long id, @Param ( "browseCount" ) Long browseCount, @Param ( "version" ) Long version);


  11. }


实现悲观锁的时候也有两种方式:

  1. 自行写原生SQL,然后写上for update语句。(方法:findCatalogsForUpdate)

  2. 使用@Lock注解,并且设置值为LockModeType.PESSIMISTIC_WRITE即可代表行级锁。

还有我写的测试类,方便大家进行测试:


  1. package com.hqs.dblock;


  2. import org.junit. Test ;

  3. import org.junit.runner. RunWith ;

  4. import org.springframework.beans.factory.annotation. Autowired ;

  5. import org.springframework.boot.test.context. SpringBootTest ;

  6. import org.springframework.boot.test.web.client. TestRestTemplate ;

  7. import org.springframework.test.context.junit4. SpringRunner ;

  8. import org.springframework.util. LinkedMultiValueMap ;

  9. import org.springframework.util. MultiValueMap ;


  10. @RunWith ( SpringRunner . class )

  11. @SpringBootTest (classes = DblockApplication . class , webEnvironment = SpringBootTest . WebEnvironment .RANDOM_PORT)

  12. public class DblockApplicationTests {


  13. @Autowired

  14. private TestRestTemplate testRestTemplate;


  15. @Test

  16. public void browseCatalogTest() {

  17. String url = "http://localhost:8888/catalog" ;

  18. for ( int i = 0 ; i < 100 ; i++) {

  19. final int num = i;

  20. new Thread (() -> {

  21. MultiValueMap < String , String > params = new LinkedMultiValueMap <>();

  22. params .add( "catalogId" , "1" );

  23. params .add( "user" , "user" + num);

  24. String result = testRestTemplate.postForObject(url, params , String . class );

  25. System . out .println( "-------------" + result);

  26. }

  27. ).start();

  28. }

  29. }


  30. @Test

  31. public void browseCatalogTestRetry() {

  32. String url = "http://localhost:8888/catalogRetry" ;

  33. for ( int i = 0 ; i < 100 ; i++) {

  34. final int num = i;

  35. new Thread (() -> {

  36. MultiValueMap < String , String > params = new LinkedMultiValueMap <>();

  37. params .add( "catalogId" , "1" );

  38. params .add( "user" , "user" + num);

  39. String result = testRestTemplate.postForObject(url, params , String . class );

  40. System . out .println( "-------------"







请到「今天看啥」查看全文