当前位置:首页 > 问答 > 正文

数据库里怎么弄悲观锁,设置方法和操作步骤全解析

关于在数据库里怎么弄悲观锁,它的核心思想很简单,我先用,谁也别动”,当你觉得一条数据很可能被别人同时修改时,你就在操作它之前,先把它“锁”上,等你自己的事情全部做完、确认无误后,再放开锁,这样其他想动这条数据的人就只能等着,下面以最常见的MySQL数据库为例,详细说明设置方法和操作步骤。

数据库里怎么弄悲观锁,设置方法和操作步骤全解析

第一步:理解悲观锁依赖的基础——事务 悲观锁不是单独存在的,它必须和数据库的“事务”一起使用,你可以把事务理解为一个不可分割的工作包,里面的所有操作要么全部成功,要么全部失败,不会只做一半,在数据库里,你通常用 START TRANSACTION; 或者 BEGIN; 来开始一个事务,用 COMMIT; 来提交所有更改,用 ROLLBACK; 来回滚、取消所有更改,悲观锁就在这个事务过程中生效。

数据库里怎么弄悲观锁,设置方法和操作步骤全解析

第二步:掌握核心的加锁语句——SELECT ... FOR UPDATE 这是实现悲观锁最常用的方法,它不是一条独立的锁命令,而是在你查询数据时,就告诉数据库:“我要修改这些数据,请把它们锁住。” 操作步骤是这样的:

数据库里怎么弄悲观锁,设置方法和操作步骤全解析

  1. 开启一个事务。
  2. 执行带 FOR UPDATE 的查询语句,你想锁住商品表中ID为1的商品信息,语句是:SELECT * FROM 商品表 WHERE id = 1 FOR UPDATE;
  3. 这时,数据库就会给这条id=1的记录加上“排他锁”,这意味着:
    • 在这个事务提交或回滚之前,其他任何事务都不能再用 SELECT ... FOR UPDATE 或者 SELECT ... LOCK IN SHARE MODE(另一种锁)来读取这条记录。
    • 更重要的是,其他事务执行普通的 UPDATEDELETE 操作也会被卡住,一直等待你的锁释放。
  4. 你在当前事务里放心地进行你的业务操作,比如检查库存、计算新值。
  5. 执行更新操作:UPDATE 商品表 SET 库存 = 新库存值 WHERE id = 1;
  6. 提交事务:COMMIT;,提交成功后,锁自动释放,其他在等待的事务才能继续操作这条数据。

第三步:了解另一种读锁——SELECT ... LOCK IN SHARE MODE 这是一种力度稍轻的悲观锁,叫“共享锁”,它的操作步骤和上面类似,但含义不同:

  1. 开启事务。
  2. 执行 SELECT * FROM 商品表 WHERE id = 1 LOCK IN SHARE MODE;
  3. 这时,你给这条记录加的是“共享锁”,这意味着:
    • 其他事务可以也使用 SELECT ... LOCK IN SHARE MODE 来读取这条数据,大家共享这把锁。
    • 任何事务(包括你自己)都不能修改这条数据,如果有事务尝试用 FOR UPDATE 加排他锁或者直接 UPDATE,也会被卡住等待。
  4. 这种锁适合用在“你只想确保在你读取后到修改前,数据绝对不被别人修改”的场景,但你自己可能不是立即修改,不过在实际中,FOR UPDATE 用得更多。

重要的注意事项和操作细节(根据常见数据库实践总结):

  1. 锁的释放时机:悲观锁的锁只有在事务结束(COMMITROLLBACK)时才会释放,如果你开了事务、加了锁,但长时间不提交,就会导致其他操作一直等待,可能拖垮系统,所以操作要快。
  2. 锁等待超时:如果一条记录被一个事务锁住了,另一个事务也想去锁它,默认会一直等,可以设置“等待超时”时间,比如在MySQL中设置 innodb_lock_wait_timeout 参数,超过时间还等不到锁,数据库会报错,避免无限等待。
  3. 死锁问题:这是最需要注意的,比如事务A锁了记录1,想去锁记录2;同时事务B锁了记录2,想去锁记录1,两个人互相等,就“死锁”了,数据库会发现这种情况,并强制回滚其中一个事务,让它报错失败,你的程序需要能处理这种错误,通常就是捕获异常后重试一遍操作。
  4. 一定要用索引:你的 WHERE 条件(比如上面的 id = 1)必须用到索引列(比如主键id),如果你用 WHERE 商品名 = 'xxx' 而“商品名”这列没索引,数据库可能会锁住整张表,性能会急剧下降。
  5. 不同数据库的差异
    • Oracle:用法和MySQL的 FOR UPDATE 非常相似。
    • SQL Server:在查询时使用 WITH (UPDLOCK) 这样的提示来实现类似效果,SELECT * FROM 商品表 WITH (UPDLOCK) WHERE id = 1
    • PostgreSQL:也支持 FOR UPDATE,行为大同小异。

总结操作步骤全流程:

  1. 开启数据库事务(BEGIN;)。
  2. 使用带 FOR UPDATE 子句的SELECT语句,查询并锁定目标数据行(确保WHERE条件用索引)。
  3. 在程序中进行业务逻辑判断和计算。
  4. 执行UPDATE语句修改已被锁定的数据。
  5. 提交事务(COMMIT;),释放锁,如果过程中发生错误,则回滚事务(ROLLBACK;),锁也会释放。

最后强调,悲观锁是一种强力但消耗性能的工具,它通过“先锁后改”保证了绝对的安全,但会让其他操作排队,在高并发场景下可能成为瓶颈,只有在冲突确实频繁发生、或者业务逻辑要求强一致性的关键环节(如扣减库存、支付余额)才考虑使用它,对于并发不高或者可以接受偶尔重试的场景,乐观锁(通过版本号控制)可能是更轻量、性能更好的选择。

备用