mysql更新锁实现代码,mysql加锁过程详解

本文目录一览:

MySQL – for update 行锁 表锁

for update 的作用是在查询的时候为行加上排它锁,当一个事务的操作未完成时候,其他事务可以读取但是不能写入或更新。

它的典型使用场景是 高并发并且对于数据的准确性有很高要求 ,比如金钱、库存等,一般这种操作都是很长一串并且开启事务的,假如现在要对库存进行操作,在刚开始读的时候是1,然后马上另外一个进程将库存更新为0了,但事务还没结束,会一直用1进行后续的逻辑,就会有问题,所以需要用for upate 加锁防止出错。

行锁的具体实现算法有三种:record lock、gap lock以及next-key lock。

只在可重复读或以上隔离级别下的特定操作才会取得 gap lock 或 next-key lock,在 Select、Update 和 Delete 时,除了基于唯一索引的查询之外,其它索引查询时都会获取 gap lock 或 next-key lock,即锁住其扫描的范围。主键索引也属于唯一索引,所以主键索引是不会使用 gap lock 或 next-key lock

for update 仅适用于InnoDB,并且必须开启事务,在begin与commit之间才生效。

select 语句默认不获取任何锁,所以是可以读被其它事务持有排它锁的数据的!

InnoDB 既实现了行锁,也实现了表锁。

当有明确指定的主键/索引时候,是行级锁,否则是表级锁

假设表 user,存在有id跟name字段,id是主键,有5条数据。

明确指定主键,并且有此记录,行级锁

无主键/索引,表级锁

主键/索引不明确,表级锁

明确指定主键/索引,若查无此记录,无锁

参考博文:

Java如何实现对Mysql数据库的行锁

下面通过一个例子来说明

场景如下:

用户账户有余额,当发生交易时,需要实时更新余额。这里如果发生并发问题,那么会造成用户余额和实际交易的不一致,这对公司和客户来说都是很危险的。

那么如何避免:

网上查了下,有以下两种方法:

1、使用悲观锁

当需要变更余额时,通过代码在事务中对当前需要更新的记录设置for update行锁,然后开始正常的查询和更新操作

这样,其他的事务只能等待该事务完成后方可操作

当然要特别注意,如果使用了Spring的事务注解,需要配置一下:

!– (事务管理)transaction manager, use JtaTransactionManager for global tx —

bean id=”transactionManager”

class=”org.springframework.jdbc.datasource.DataSourceTransactionManager”

property name=”dataSource” ref=”dataSource” /

/bean

!– 使用annotation定义事务 —

tx:annotation-driven transaction-manager=”transactionManager” /

在指定代码处添加事务注解

@Transactional

@Override

public boolean increaseBalanceByLock(Long userId, BigDecimal amount)

throws ValidateException {

long time = System.currentTimeMillis();

//获取对记录的锁定

UserBalance balance = userBalanceDao.getLock(userId);

LOGGER.info(“[lock] start. time: {}”, time);

if (null == balance) {

throw new ValidateException(

ValidateErrorCode.ERRORCODE_BALANCE_NOTEXIST,

“user balance is not exist”);

}

boolean result = userBalanceDao.increaseBalanceByLock(balance, amount);

long timeEnd = System.currentTimeMillis();

LOGGER.info(“[lock] end. time: {}”, timeEnd);

return result;

}

MyBatis中的锁定方式,实际测试该方法确实可以有效控制,不过在大并发量的情况下,可能会有性能问题吧

select id=”getLock” resultMap=”BaseResultMap” parameterType=”java.lang.Long”

![CDATA[

select * from user_balance where id=#{id,jdbcType=BIGINT} for update;

]]

/select

2、使用乐观锁

这个方法也同样可以解决场景中描述的问题(我认为比较适合并不频繁的操作):

设计表的时候增加一个version(版本控制字段),每次需要更新余额的时候,先获取对象,update的时候根据version和id为条件去更新,如果更新回来的数量为0,说明version已经变更

需要重复一次更新操作,如下:sql脚本

update user_balance set Balance = #{balance,jdbcType=DECIMAL},Version = Version+1 where Id = #{id,jdbcType=BIGINT} and Version = #{version,jdbcType=BIGINT}

这是一种不使用数据库锁的方法,解决方式也很巧妙。当然,在大量并发的情况下,一次扣款需要重复多次的操作才能成功,还是有不足之处的。不知道还有没有更好的方法。

用 MySQL 实现分布式锁,你听过吗?

以前参加过一个库存系统,由于其业务复杂性,搞了很多个应用来支撑。这样的话一份库存数据就有可能同时有多个应用来修改库存数据。

比如说,有定时任务域xx.cron,和SystemA域和SystemB域这几个JAVA应用,可能同时修改同一份库存数据。如果不做协调的话,就会有脏数据出现。

对于跨JAVA进程的线程协调,可以借助外部环境,例如DB或者Redis。下文介绍一下如何使用DB来实现分布式锁。

本文设计的分布式锁的交互方式如下:

在使用synchronized关键字的时候,必须指定一个锁对象。

进程内的线程可以基于obj来实现同步。obj在这里可以理解为一个锁对象。如果线程要进入synchronized代码块里,必须先持有obj对象上的锁。这种锁是JAVA里面的内置锁,创建的过程是线程安全的。那么借助DB,如何保证创建锁的过程是线程安全的呢?

可以利用DB中的UNIQUE KEY特性,一旦出现了重复的key,由于UNIQUE KEY的唯一性,会抛出异常的。在JAVA里面,是 SQLIntegrityConstraintViolationException 异常。

transaction_id是事务Id,比如说,可以用

来组装一个transaction_id,表示某仓库某销售模式下的某个条码资源。不同条码,当然就有不同的transaction_id。如果有两个应用,拿着相同的transaction_id来创建锁资源的时候,只能有一个应用创建成功。

在写操作频繁的业务系统中,通常会进行分库,以降低单数据库写入的压力,并提高写操作的吞吐量。如果使用了分库,那么业务数据自然也都分配到各个数据库上了。

在这种水平切分的多数据库上使用DB分布式锁,可以自定义一个DataSouce列表。并暴露一个 getConnection(String transactionId) 方法,按照transactionId找到对应的Connection。

实现代码如下:

首先编写一个initDataSourceList方法,并利用Spring的PostConstruct注解初始化一个DataSource 列表。相关的DB配置从db.properties读取。

DataSource使用阿里的DruidDataSource。

接着最重要的一个实现getConnection(String transactionId)方法。实现原理很简单,获取transactionId的hashcode,并对DataSource的长度取模即可。

连接池列表设计好后,就可以实现往distributed_lock表插入数据了。

接下来利用DB的 select for update 特性来锁住线程。当多个线程根据相同的transactionId并发同时操作 select for update 的时候,只有一个线程能成功,其他线程都block住,直到 select for update 成功的线程使用commit操作后,block住的所有线程的其中一个线程才能开始干活。

我们在上面的DistributedLock类中创建一个lock方法。

当线程执行完任务后,必须手动的执行解锁操作,之前被锁住的线程才能继续干活。在我们上面的实现中,其实就是获取到当时 select for update 成功的线程对应的Connection,并实行commit操作即可。

那么如何获取到呢?我们可以利用ThreadLocal。首先在DistributedLock类中定义

每次调用lock方法的时候,把Connection放置到ThreadLocal里面。我们修改lock方法。

这样子,当获取到Connection后,将其设置到ThreadLocal中,如果lock方法出现异常,则将其从ThreadLocal中移除掉。

有了这几步后,我们可以来实现解锁操作了。我们在DistributedLock添加一个unlock方法。

毕竟是利用DB来实现分布式锁,对DB还是造成一定的压力。当时考虑使用DB做分布式的一个重要原因是,我们的应用是后端应用,平时流量不大的,反而关键的是要保证库存数据的正确性。对于像前端库存系统,比如添加购物车占用库存等操作,最好别使用DB来实现分布式锁了。

如果想锁住多份数据该怎么实现?比如说,某个库存操作,既要修改物理库存,又要修改虚拟库存,想锁住物理库存的同时,又锁住虚拟库存。其实也不是很难,参考lock方法,写一个multiLock方法,提供多个transactionId的入参,for循环处理就可以了。这个后续有时间再补上。

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/153873.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝小蓝
上一篇 2024-11-15 03:23
下一篇 2024-11-15 03:23

相关推荐

  • Python周杰伦代码用法介绍

    本文将从多个方面对Python周杰伦代码进行详细的阐述。 一、代码介绍 from urllib.request import urlopen from bs4 import Bea…

    编程 2025-04-29
  • Java Bean加载过程

    Java Bean加载过程涉及到类加载器、反射机制和Java虚拟机的执行过程。在本文中,将从这三个方面详细阐述Java Bean加载的过程。 一、类加载器 类加载器是Java虚拟机…

    编程 2025-04-29
  • Python字符串宽度不限制怎么打代码

    本文将为大家详细介绍Python字符串宽度不限制时如何打代码的几个方面。 一、保持代码风格的统一 在Python字符串宽度不限制的情况下,我们可以写出很长很长的一行代码。但是,为了…

    编程 2025-04-29
  • Python基础代码用法介绍

    本文将从多个方面对Python基础代码进行解析和详细阐述,力求让读者深刻理解Python基础代码。通过本文的学习,相信大家对Python的学习和应用会更加轻松和高效。 一、变量和数…

    编程 2025-04-29
  • Python满天星代码:让编程变得更加简单

    本文将从多个方面详细阐述Python满天星代码,为大家介绍它的优点以及如何在编程中使用。无论是刚刚接触编程还是资深程序员,都能从中获得一定的收获。 一、简介 Python满天星代码…

    编程 2025-04-29
  • 仓库管理系统代码设计Python

    这篇文章将详细探讨如何设计一个基于Python的仓库管理系统。 一、基本需求 在着手设计之前,我们首先需要确定仓库管理系统的基本需求。 我们可以将需求分为以下几个方面: 1、库存管…

    编程 2025-04-29
  • 写代码新手教程

    本文将从语言选择、学习方法、编码规范以及常见问题解答等多个方面,为编程新手提供实用、简明的教程。 一、语言选择 作为编程新手,选择一门编程语言是很关键的一步。以下是几个有代表性的编…

    编程 2025-04-29
  • Python实现简易心形代码

    在这个文章中,我们将会介绍如何用Python语言编写一个非常简单的代码来生成一个心形图案。我们将会从安装Python开始介绍,逐步深入了解如何实现这一任务。 一、安装Python …

    编程 2025-04-29
  • 怎么写不影响Python运行的长段代码

    在Python编程的过程中,我们不可避免地需要编写一些长段代码,包括函数、类、复杂的控制语句等等。在编写这些代码时,我们需要考虑代码可读性、易用性以及对Python运行性能的影响。…

    编程 2025-04-29
  • 北化教务管理系统介绍及开发代码示例

    本文将从多个方面对北化教务管理系统进行介绍及开发代码示例,帮助开发者更好地理解和应用该系统。 一、项目介绍 北化教务管理系统是一款针对高校学生和教职工的综合信息管理系统。系统实现的…

    编程 2025-04-29

发表回复

登录后才能评论