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

    文章

    4084

    浏览量

    68549

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

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

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    面试必看:排队自旋之MCS的实现原理与关键考点

    在并发编程面试中,“” 是绕不开的核心话题,而自旋作为轻量级的代表,其优化方案更是高频考点。
    的头像 发表于 02-09 16:51 857次阅读
    面试必看:排队自旋<b class='flag-5'>锁</b>之MCS<b class='flag-5'>锁</b>的实现原理与关键考点

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

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

    国产数据库的AI战事

    国产数据库硝烟再起,Vastbase V100构筑企业智能基座
    的头像 发表于 10-24 20:45 4429次阅读
    国产<b class='flag-5'>数据库</b>的AI战事

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

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

    什么是电磁控制板?24路控板的使用步骤概述

    电磁控制板是一种专用于控制电磁工作状态的电子装置,它可以接收指令并对电磁进行通断电操作,从而实现对相关设备的锁定和解锁控制。电磁本身依靠电流产生磁场作用力来吸附或释放
    的头像 发表于 07-14 16:26 943次阅读
    什么是电磁<b class='flag-5'>锁</b>控制板?24路<b class='flag-5'>锁</b>控板的使用步骤概述

    三款主流国产数据库的技术特点

    随着数字经济的快速发展和数据安全要求的提升,国产数据库正迎来前所未有的发展机遇。在信创浪潮推动下,达梦数据库、TiDB、华为高斯数据库等国产数据库
    的头像 发表于 07-14 11:08 1346次阅读

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

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

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

    MongoDB数据库数据恢复环境: 一台操作系统为Windows Server的虚拟机上部署MongoDB数据库。 MongoDB数据库故障: 工作人员在MongoDB服务仍
    的头像 发表于 07-01 11:13 785次阅读
    <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 844次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—SQL Server<b class='flag-5'>数据库</b>被加密如何恢复<b class='flag-5'>数据</b>?

    离在线语音芯片WT2606A在智能行业的应用场景

    离在线语音芯片 WT2606A 在智能行业的应用场景主要围绕语音交互升级、功能扩展及用户体验优化展开,具体包括以下核心方向: 一、基础语音提示与操作引导 开锁流程交互 用户通过指纹、密码或人脸识别
    的头像 发表于 06-24 17:46 861次阅读
    离在线语音芯片WT2606A在智能<b class='flag-5'>锁</b>行业的应<b class='flag-5'>用场景</b>

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

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

    oracle数据恢复—oracle数据库误执行错误truncate命令如何恢复数据

    oracle数据库误执行truncate命令导致数据丢失是一种常见情况。通常情况下,oracle数据库误操作删除数据只需要通过备份恢复数据
    的头像 发表于 06-05 16:01 1799次阅读
    oracle<b class='flag-5'>数据</b>恢复—oracle<b class='flag-5'>数据库</b>误执行错误truncate命令如何恢复<b class='flag-5'>数据</b>?

    MySQL数据库采集网关是什么?有什么功能?

    MySQL数据库采集网关是一种用于连接、采集、处理并传输数据到MySQL数据库的中间设备或软件系统,通常部署在数据源与MySQL数据库之间,
    的头像 发表于 05-26 15:20 821次阅读

    SQLSERVER数据库是什么

    SQL Server 是由微软公司开发的一款 关系型数据库管理系统(RDBMS) ,用于存储、管理和检索结构化数据。它是企业级应用中广泛使用的数据库解决方案之一,尤其适用于Windows平台,但也
    的头像 发表于 05-26 09:19 1303次阅读

    MySQL数据库是什么

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