0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

数据库的乐观锁和悲观锁的使用场景

电子工程师 来源:ZYD 2019-02-25 11:04 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

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

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

它们使用的场景如下:

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

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

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

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

比如

update table set column = value

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

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

比如

update table set column='value'for update

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

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

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

表的结构非常简单:

create table catalog (

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

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

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

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

PRIMARY KEY(id)

) ENGINE=INNODB DEFAULT CHARSET=utf8;

CREATE table browse (

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

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

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

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

PRIMARY KEY(id)

) ENGINE=INNODB DEFAULT CHARSET=utf8;

POM.XML的依赖如下:

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

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.1.1.RELEASE

com.hqs

dblock

1.0-SNAPSHOT

dblock

Demo project for Spring Boot

1.8

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-devtools

runtime

mysql

mysql-connector-java

runtime

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-starter-data-jpa

mysql

mysql-connector-java

org.projectlombok

lombok

true

org.aspectj

aspectjweaver

1.8.4

org.springframework.boot

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匹配和更新,是不是很简单。

publicinterfaceCatalogRepositoryextendsCrudRepository {

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

Optional findCatalogsForUpdate(@Param("id") Long id);

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

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

Optional findCatalogWithPessimisticLock(@Param("id") Long id);

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

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

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

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

}

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

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

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

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

package com.hqs.dblock;

import org.junit.Test;

import org.junit.runner.RunWith;

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

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

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

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

import org.springframework.util.LinkedMultiValueMap;

import org.springframework.util.MultiValueMap;

@RunWith(SpringRunner.class)

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

publicclassDblockApplicationTests {

@Autowired

privateTestRestTemplate testRestTemplate;

@Test

publicvoid browseCatalogTest() {

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

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

finalint num = i;

newThread(() -> {

MultiValueMap params = newLinkedMultiValueMap<>();

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

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

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

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

}

).start();

}

}

@Test

publicvoid browseCatalogTestRetry() {

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

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

finalint num = i;

newThread(() -> {

MultiValueMap params = newLinkedMultiValueMap<>();

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

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

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

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

}

).start();

}

}

}

调用100次,即一个商品可以浏览一百次,采用悲观锁,catalog表的数据都是100,并且browse表也是100条记录。采用乐观锁的时候,因为版本号的匹配关系,那么会有一些记录丢失,但是这两个表的数据是可以对应上的。

乐观锁失败后会抛出ObjectOptimisticLockingFailureException,那么我们就针对这块考虑一下重试,下面我就自定义了一个注解,用于做切面。

package com.hqs.dblock.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceRetryOnFailure {

}

针对注解进行切面,见如下代码。我设置了最大重试次数5,然后超过5次后就不再重试。

package com.hqs.dblock.aspect;

import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.hibernate.StaleObjectStateException;

import org.springframework.orm.ObjectOptimisticLockingFailureException;

import org.springframework.stereotype.Component;

@Slf4j

@Aspect

@Component

publicclassRetryAspect {

publicstaticfinalint MAX_RETRY_TIMES = 5;//max retry times

@Pointcut("@annotation(com.hqs.dblock.annotation.RetryOnFailure)") //self-defined pointcount for RetryOnFailure

publicvoid retryOnFailure(){}

@Around("retryOnFailure()") //around can be execute before and after the point

publicObject doConcurrentOperation(ProceedingJoinPoint pjp) throwsThrowable {

int attempts = 0;

do {

attempts++;

try {

pjp.proceed();

} catch (Exception e) {

if(e instanceofObjectOptimisticLockingFailureException ||

e instanceofStaleObjectStateException) {

log.info("retrying....times:{}", attempts);

if(attempts > MAX_RETRY_TIMES) {

log.info("retry excceed the max times..");

throw e;

}

}

}

} while (attempts < MAX_RETRY_TIMES);

returnnull;

}

}

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据库
    +关注

    关注

    7

    文章

    3993

    浏览量

    67734

原文标题:聊聊数据库乐观锁和悲观锁

文章出处:【微信号:DBDevs,微信公众号:数据分析与开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    智能指纹CW32系列有哪些应用场景

    智能指纹CW32系列有哪些应用场景
    发表于 12-05 06:34

    数据库数据恢复—服务器异常断电导致Oracle数据库故障的数据恢复案例

    Oracle数据库故障: 某公司一台服务器上部署Oracle数据库。服务器意外断电导致数据库报错,报错内容为“system01.dbf需要更多的恢复来保持一致性”。该Oracle数据库
    的头像 发表于 07-24 11:12 436次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—服务器异常断电导致Oracle<b class='flag-5'>数据库</b>故障的<b class='flag-5'>数据</b>恢复案例

    milvus向量数据库的主要特性和应用场景

    Milvus 是一个开源的向量数据库,专门为处理和分析大规模向量数据而设计。它适用于需要高效存储、检索和管理向量数据的应用场景,如机器学习、人工智能、计算机视觉和自然语言处理等。
    的头像 发表于 07-04 11:36 729次阅读
    milvus向量<b class='flag-5'>数据库</b>的主要特性和应<b class='flag-5'>用场景</b>

    数据库数据恢复—MongoDB数据库文件丢失的数据恢复案例

    MongoDB数据库数据恢复环境: 一台操作系统为Windows Server的虚拟机上部署MongoDB数据库。 MongoDB数据库故障: 工作人员在MongoDB服务仍
    的头像 发表于 07-01 11:13 456次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—MongoDB<b class='flag-5'>数据库</b>文件丢失的<b class='flag-5'>数据</b>恢复案例

    数据库数据恢复—SQL Server数据库被加密如何恢复数据

    SQL Server数据库故障: SQL Server数据库被加密,无法使用。 数据库MDF、LDF、log日志文件名字被篡改。
    的头像 发表于 06-25 13:54 506次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—SQL Server<b class='flag-5'>数据库</b>被加密如何恢复<b class='flag-5'>数据</b>?

    一文读懂Allegro先进磁性开关和存器

    Allegro 拥有丰富的霍尔效应和隧道磁阻(TMR)开关及存器产品,可广泛应用于汽车、工业和消费电子等领域。本应用笔记旨在提供分步选型流程,协助设计师为具体应用场景选择适配的 Allegro
    的头像 发表于 06-12 17:26 1585次阅读
    一文读懂Allegro先进磁性开关和<b class='flag-5'>锁</b>存器

    MySQL数据库是什么

    开发、企业应用和大数据场景。以下是其核心特性和应用场景的详细说明: 核心特性 关系型数据库模型 数据以 表(Table) 形式组织,表由行(
    的头像 发表于 05-23 09:18 914次阅读

    数据库数据恢复——MongoDB数据库文件拷贝后服务无法启动的数据恢复

    MongoDB数据库数据恢复环境: 一台Windows Server操作系统虚拟机上部署MongoDB数据库。 MongoDB数据库故障: 管理员在未关闭MongoDB服务的
    的头像 发表于 04-09 11:34 652次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复——MongoDB<b class='flag-5'>数据库</b>文件拷贝后服务无法启动的<b class='flag-5'>数据</b>恢复

    数据库数据恢复—SQL Server附加数据库提示“错误 823”的数据恢复案例

    SQL Server数据库附加数据库过程中比较常见的报错是“错误 823”,附加数据库失败。 如果数据库有备份则只需还原备份即可。但是如果没有备份,备份时间太久,或者其他原因导致备份
    的头像 发表于 02-28 11:38 823次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—SQL Server附加<b class='flag-5'>数据库</b>提示“错误 823”的<b class='flag-5'>数据</b>恢复案例

    电路模组选择指南

    。要解除自,通常需要一个额外的信号来使触点断开。 2. 自电路模组的类型 自电路模组可以根据不同的应用场景和需求选择不同类型的继电器: 电磁继电器 :最传统的自
    的头像 发表于 01-18 10:08 1183次阅读

    电路的类型和特点

    和应用场景分为以下几种类型: 机械式自电路 : 这种类型的自电路依赖于机械装置来保持电路的状态。例如,一个简单的开关可以设计成在按下后自动锁定,直到再次按下才会释放。 电子式自
    的头像 发表于 01-18 10:03 1635次阅读

    MySQL数据库的安装

    MySQL数据库的安装 【一】各种数据库的端口 MySQL :3306 Redis :6379 MongoDB :27017 Django :8000 flask :5000 【二】MySQL 介绍
    的头像 发表于 01-14 11:25 858次阅读
    MySQL<b class='flag-5'>数据库</b>的安装

    数据库是哪种数据库类型?

    数据库是一种部署在虚拟计算环境中的数据库,它融合了云计算的弹性和可扩展性,为用户提供高效、灵活的数据库服务。云数据库主要分为两大类:关系型数据库
    的头像 发表于 01-07 10:22 803次阅读

    数据库数据恢复—Mysql数据库表记录丢失的数据恢复流程

    Mysql数据库故障: Mysql数据库表记录丢失。 Mysql数据库故障表现: 1、Mysql数据库表中无任何数据或只有部分
    的头像 发表于 12-16 11:05 1000次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—Mysql<b class='flag-5'>数据库</b>表记录丢失的<b class='flag-5'>数据</b>恢复流程

    数据库数据恢复—MYSQL数据库ibdata1文件损坏的数据恢复案例

    mysql数据库故障: mysql数据库文件ibdata1、MYI、MYD损坏。 故障表现:1、数据库无法进行查询等操作;2、使用mysqlcheck和myisamchk无法修复数据库
    的头像 发表于 12-09 11:05 1049次阅读