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

    文章

    3750

    浏览量

    64217

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

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

收藏 人收藏

    评论

    相关推荐

    多维表格数据库Teable的适用场景

    Teable多维表格数据库是一款功能强大的云端数据库和协作工具,结合了电子表格的灵活性和数据库的强大功能,适用企业内部项目管理 数据收集与整理 内容管理与创意协作 客户关系管理 项目跟
    的头像 发表于 10-31 15:48 71次阅读

    数据库数据恢复—通过拼接数据库碎片恢复SQLserver数据库

    一个运行在存储上的SQLServer数据库,有1000多个文件,大小几十TB。数据库每10天生成一个NDF文件,每个NDF几百GB大小。数据库包含两个LDF文件。 存储损坏,数据库
    的头像 发表于 10-31 13:21 72次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—通过拼接<b class='flag-5'>数据库</b>碎片恢复SQLserver<b class='flag-5'>数据库</b>

    数据库数据恢复—SQL Server数据库出现823错误的数据恢复案例

    SQL Server数据库故障: SQL Server附加数据库出现错误823,附加数据库失败。数据库没有备份,无法通过备份恢复数据库
    的头像 发表于 09-20 11:46 250次阅读
    <b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复—SQL Server<b class='flag-5'>数据库</b>出现823错误的<b class='flag-5'>数据</b>恢复案例

    互斥和自旋的实现原理

    互斥和自旋是操作系统中常用的同步机制,用于控制对共享资源的访问,以避免多个线程或进程同时访问同一资源,从而引发数据不一致或竞争条件等问题。 互斥(Mutex) 互斥
    的头像 发表于 07-10 10:07 376次阅读

    自旋和互斥的使用场景是什么

    自旋和互斥是两种常见的同步机制,它们在多线程编程中被广泛使用。在本文中,我们将介绍自旋和互斥的使用场景,以及它们在不同
    的头像 发表于 07-10 10:05 805次阅读

    数据库数据恢复】Oracle数据库ASM实例无法挂载的数据恢复案例

    oracle数据库ASM磁盘组掉线,ASM实例不能挂载。数据库管理员尝试修复数据库,但是没有成功。
    的头像 发表于 02-01 17:39 444次阅读
    【<b class='flag-5'>数据库</b><b class='flag-5'>数据</b>恢复】Oracle<b class='flag-5'>数据库</b>ASM实例无法挂载的<b class='flag-5'>数据</b>恢复案例

    分布式的三种实现方式

    ,下面将分别介绍三种常见的实现方式。 一、基于数据库实现的分布式 在分布式系统中,数据库是最常用的共享资源之一。因此,可以通过数据库的特性来实现分布式
    的头像 发表于 12-28 10:01 824次阅读

    关于JSON数据库

    如何理解JSON数据库?作为NoSQL数据库的一种类型,JSON数据库有哪些优势呢?JSON数据库如何运作,它为应用程序开发者带来了哪些价值呢?
    的头像 发表于 12-06 13:46 815次阅读
    关于JSON<b class='flag-5'>数据库</b>

    redis的原理和使用场景

    、消息队列、实时分析、排行榜和计数器等场景。本文将详细介绍Redis的原理和使用场景。 一、Redis的原理 Redis的原理主要包括以下几个方面: 内存数据库:Redis是一种内存数据库
    的头像 发表于 12-04 16:29 549次阅读

    redis分布式的应用场景有哪些

    Redis分布式是一种基于Redis实现的分布式机制,可以在分布式环境下确保资源的独占性,避免并发访问时的数据争用问题。下面将详细介绍Redis分布式的应
    的头像 发表于 12-04 11:21 1348次阅读

    java redis处理并发代码

    在并发编程中,一个常见的问题是如何确保多个线程安全地访问共享资源,避免产生竞态条件和数据异常。而Redis作为一种高性能的内存数据库,可以提供分布式的功能,通过Redis,我们可以
    的头像 发表于 12-04 11:04 893次阅读

    什么是JSON数据库

    如何理解JSON数据库?作为NoSQL数据库的一种类型,JSON数据库有哪些优势呢?JSON数据库如何运作,它为应用程序开发者带来了哪些价值呢?文章速览:什么是JSON什么是JSON
    的头像 发表于 12-02 08:04 798次阅读
    什么是JSON<b class='flag-5'>数据库</b>

    NoSQL 数据库如何选型

    什么是NoSQL数据库?为什么要使用NoSQL数据库?键值数据库内存键值数据库文档数据库列式数据库
    的头像 发表于 11-26 08:05 419次阅读
    NoSQL <b class='flag-5'>数据库</b>如何选型

    SQLite、MySQL和PostgreSQL的差异与应用场景

    一个完整的IT系统一般少不了数据库系统的支撑,大量的数据需要保存到数据库中。不同的数据库在使用场景和性能上,有一定的差异。IT系统需要根据运
    的头像 发表于 11-24 15:44 2047次阅读

    什么是NoSQL?NoSQL数据库的使用场景和架构介绍

    NoSQL,全称为Not Only SQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库数据库管理系统的统称。
    的头像 发表于 11-09 11:01 2456次阅读
    什么是NoSQL?NoSQL<b class='flag-5'>数据库</b>的使<b class='flag-5'>用场景</b>和架构介绍