首页 > 数据库 >数据库悲观锁和乐观锁的区别

数据库悲观锁和乐观锁的区别

时间:2024-09-04 11:25:46浏览次数:10  
标签:事务 场景 区别 版本号 数据库 更新 悲观 数据

前言

MySQL本身不直接提供悲观锁(Pessimistic Locking)和乐观锁(Optimistic Locking)的实现机制,因为这些锁的概念通常是在应用层面通过不同的策略和工具来实现的。然而,我们可以利用MySQL的一些特性来模拟或支持这两种锁的行为。
在这里插入图片描述

一、什么是乐观锁和悲观锁?

乐观锁(Optimistic Locking)和悲观锁(Pessimistic Locking)是两种在数据库管理和并发控制中常用的策略,它们以不同的方式处理数据访问和修改时可能发生的冲突。下面将详细描述这两种锁的工作机制。

1. 悲观锁(Pessimistic Locking)

悲观锁基于一种悲观的态度来处理并发问题,它假设数据冲突将会频繁发生,因此在数据被读取的同时就会被锁定,以防止其他事务对数据进行修改,直到当前事务完成。
在这里插入图片描述

工作机制

  1. 加锁:当事务需要读取或修改某个数据时,它首先会对该数据加锁。在MySQL中,这通常是通过SELECT ... FOR UPDATE语句实现的,它会锁定读取的行直到事务结束(提交或回滚)。

  2. 数据操作:事务在持有锁的情况下对数据进行读取或修改。

  3. 解锁:当事务提交时,锁会被释放,其他事务才能访问这些数据。如果事务回滚,锁也会被释放,但所做的修改不会保存到数据库中。

  4. 等待与冲突解决:如果有多个事务试图对同一数据进行加锁,那么后来的事务将会等待,直到前面的事务释放锁。这可能会导致事务的延迟或死锁(如果两个事务相互等待对方释放锁)。

2. 乐观锁(Optimistic Locking)

乐观锁则基于一种乐观的态度来处理并发问题,它假设数据冲突将不会频繁发生,因此在数据读取时不会加锁,而是在更新数据时检查数据是否被其他事务修改过。
在这里插入图片描述

工作机制

  1. 数据读取:事务首先读取需要修改的数据及其版本号(或时间戳)。这个版本号是在数据库中额外维护的一个字段,用于跟踪数据的更新情况。

  2. 数据操作:事务在本地对读取的数据进行修改,但不立即更新到数据库中。

  3. 提交前的检查:在提交事务之前,事务会再次查询数据库中对应数据的版本号,并将其与第一步读取的版本号进行比较。

  4. 数据更新

    • 如果版本号相同,说明数据在读取和提交之间没有被其他事务修改过,事务可以安全地更新数据,并将版本号增加(或更新时间戳)。
    • 如果版本号不同,说明数据在读取和提交之间已经被其他事务修改过,此时当前事务可以选择重试(重新读取数据并尝试更新)、回滚或向应用层报告错误。
  5. 提交事务:如果数据更新成功,事务将提交,所做的修改将被保存到数据库中。

优点与缺点

  • 悲观锁的优点是能够在很大程度上避免数据冲突,但缺点是可能会导致性能问题,因为事务在持有锁期间会阻塞其他事务对数据的访问。
  • 乐观锁的优点是避免了锁的开销,提高了系统的并发性能,但缺点是当冲突发生时,可能需要事务重试,这可能会增加事务的延迟和复杂性。

在选择使用哪种锁时,需要根据具体的应用场景和性能需求进行权衡。

二、应用实践

悲观锁(Pessimistic Locking)

前面我们说到它的工作机制,悲观锁是假定冲突将频繁发生,因此在操作数据之前先锁定数据。在MySQL中,悲观锁可以通过行级锁(InnoDB存储引擎提供)或表级锁(MyISAM或InnoDB在某些情况下)来实现。

应用实践
  1. 使用SELECT … FOR UPDATE

    这是最常用的悲观锁实现方式。当事务使用SELECT ... FOR UPDATE语句读取记录时,MySQL会对这些记录加锁,其他事务必须等待锁释放才能修改这些记录。

    START TRANSACTION;
    SELECT * FROM accounts WHERE id = 100 FOR UPDATE;
    -- 在这里进行更新操作
    UPDATE accounts SET balance = balance - 100 WHERE id = 100;
    COMMIT;
    

    注意:FOR UPDATE必须在一个事务中使用,否则会立即释放锁。

  2. 使用表锁

    虽然表锁不是悲观锁的最佳实践(因为它会锁定整个表),但在某些情况下仍然可以使用。通过LOCK TABLESUNLOCK TABLES命令可以显式地锁定和解锁表。

    LOCK TABLES accounts WRITE;
    -- 在这里进行更新操作
    UPDATE accounts SET balance = balance - 100 WHERE id = 100;
    UNLOCK TABLES;
    

乐观锁(Optimistic Locking)

乐观锁假定冲突不会经常发生,因此只在提交更新时检查数据是否被其他事务修改过。这通常通过版本号(version number)或时间戳(timestamp)来实现。

应用实践
  1. 使用版本号

    在数据库表中添加一个version字段,每次更新数据时增加版本号。在更新时,检查版本号是否匹配,如果不匹配则拒绝更新。

    -- 假设version是表中的一个字段
    UPDATE accounts SET balance = balance - 100, version = version + 1 WHERE id = 100 AND version = ?;
    

    在应用中,你需要先查询当前记录的版本号,然后在更新时提交这个版本号。如果更新影响的行数为0,则表示数据在查询和更新之间被其他事务修改了。

  2. 使用时间戳

    类似于版本号,但使用时间戳字段来跟踪记录的最后一次更新时间。

    UPDATE accounts SET balance = balance - 100, last_updated = NOW() WHERE id = 100 AND last_updated = ?;
    

    同样,你需要先查询记录的last_updated时间,然后在更新时提交这个时间戳。

应用场景

乐观锁和悲观锁在数据库管理和并发控制中各有其适用的应用场景。以下是两者之间的应用场景的详细列举:

乐观锁的应用场景

  1. 读多写少的场景

    • 在这种场景下,多个事务主要进行数据的读取操作,而写操作相对较少。使用乐观锁可以减少锁的开销,提高系统的并发性能。例如,新闻网站中用户同时浏览新闻的场景,新闻内容被修改的概率较低,适合使用乐观锁。
  2. 偶尔冲突且回滚成本较低的场景

    • 当数据冲突不频繁,且回滚事务的成本低于读取数据时锁定数据的成本时,使用乐观锁可以获得更高的吞吐量。例如,在电商网站的购物车操作中,虽然多个用户可能同时操作购物车,但购物车内容被同时修改的概率较低,即使发生冲突,回滚购物车操作的成本也相对较低。
  3. 数据版本控制

    • 在需要保证数据版本一致性的场景下,可以使用乐观锁来控制数据的更新操作。通过版本号或时间戳来跟踪数据的更新情况,确保在更新数据时数据的一致性。

悲观锁的应用场景

  1. 写多读少的场景

    • 在这种场景下,多个事务主要进行数据的修改操作,而读取操作相对较少。使用悲观锁可以确保数据在修改过程中不会被其他事务干扰,保证数据的一致性。例如,在电商网站的库存扣减操作中,由于库存数据需要频繁更新,且更新操作对数据的准确性要求极高,因此适合使用悲观锁。
  2. 并发冲突较高的场景

    • 当多个事务同时对同一数据进行读写操作时,使用悲观锁可以避免数据冲突和更新丢失的问题。通过锁定数据,确保在事务完成之前其他事务无法修改该数据。例如,在银行系统的账户转账操作中,由于涉及到多个账户的金额变动,且这些变动必须同时成功或同时失败,因此需要使用悲观锁来确保数据的一致性。
  3. 数据一致性要求极高的场景

    • 在对数据一致性要求极高的场景下,如金融交易、医疗记录等,使用悲观锁可以确保数据在处理过程中不会被其他事务干扰,从而避免数据不一致的问题。

综上所述,乐观锁和悲观锁各有其适用的应用场景。在选择使用哪种锁时,需要根据具体的应用场景、性能需求和数据一致性要求来进行权衡和选择。

总结

选择悲观锁还是乐观锁取决于你的应用场景。悲观锁适合写操作频繁的场景,因为它可以减少数据冲突,但可能会增加等待时间和锁的竞争。乐观锁适合读多写少的场景,它减少了锁的开销,但在高并发写的情况下可能需要处理更多的冲突。
在实际应用中,还需要考虑事务的隔离级别、锁的粒度(行级锁、表级锁)以及应用的具体需求来选择合适的锁策略。

标签:事务,场景,区别,版本号,数据库,更新,悲观,数据
From: https://blog.csdn.net/qq_35807303/article/details/141784153

相关文章

  • 【Linux系列】SH 与 BASH 的区别:深入解析与使用案例
    ......
  • 多线程、任务、异步的区别
    Task和Thread的区别这是一个高频,深刻的问题,无论去哪都逃不过被询问这个问题。Task是基于Thread的,这是众所周知的。但是Task和Thread的联系如此简单和纯粹确实我没想到的。甚至只需要几十行代码就能呈现其原理。一个简单的模拟实例说明Task及其调度问题,这真是一篇好文章。任务体......
  • 【含文档】基于Springboot+Vue的健身俱乐部网站(含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能系统中......
  • 【问题记录】【数据库】库存或者账户流水记录修复
    1 前言大家的系统有没有关于客户资金、会员卡余额、库存记录等,这些相关信息的存储,说白了就是流水记录表。不知道大家是如何存储的,我们的存储一条记录最起码的是变动数量、变动前数量、变动后数量,这个变动前、变动后就粘的比较紧,那么当系统出现问题的时候,可能中间差一条变动,那么......
  • malloc/free 和 new/delete的区别
    malloc/free和new/delete是C++中两种不同的动态内存管理方法,它们有一些关键的区别:1.内存分配和释放机制malloc和free:函数:malloc是一个C标准库函数,用于从堆中分配指定大小的原始内存块。它返回一个void*指针,指向分配的内存块的起始位置。free用于释放由......
  • 利用LangChain构建MySQL数据库问答代理
    引言随着自然语言处理技术的飞速发展,尤其是大型语言模型(LLM)的应用日益广泛,人们对于如何更高效地与这些模型交互产生了浓厚的兴趣。LangChain是一个旨在简化与语言模型集成的开源框架,它使得开发者能够轻松地构建出强大的应用程序。本文将介绍如何使用LangChain结合MySQL数据......
  • 流式dma和一致性dma的区别
    流式DMA(StreamingDMA)和一致性DMA(ConsistentDMA)是两种不同的内存映射模式,用于DMA(直接内存访问)操作。它们的主要区别在于缓存一致性、性能和使用场景。以下是这两者的详细区别:1.流式DMA(StreamingDMA)缓存一致性:流式DMA不保证缓存的一致性。在进行DMA操作前,需要显式......
  • 达梦数据库的系统视图v$ifun_arg
    达梦数据库的系统视图v$ifun_arg在达梦数据库(DMDatabase)中,V$IFUN_ARG系统视图提供了关于存储函数(或存储过程)参数的详细信息。它是与函数参数相关的系统表,可以帮助数据库管理员和开发人员查看和管理数据库中所有函数参数的信息。使用场景参数管理:查看数据库中所有存......
  • 数据库实验 SQL server sduwh caohai
    问题一:根据E-R图写出关系模式,标注主键和外键 学生关系模式:Student(StudentNum,StudentName,StudentSex,StudentAge,StudentPhone,StudentBrith,Speciality)其中主键为学号StudentNum,没有外键。课程关系模式:Course(Coursenum,CourseName,Credit,Classhour,ClassType)其中主键为......
  • 数据库系统 第33节 复杂查询优化
    复杂查询优化是数据库管理系统的优化器负责的一项关键任务,它涉及如何有效地处理那些包含多个表联接、子查询以及聚合函数的SQL语句。优化器的工作是生成一个执行计划,该计划尽可能高效地完成查询请求。下面我将简要介绍一些优化技巧及其背后的原理。索引使用索引可以极大地......