SQL with(unlock)与with(readpast)

博客介绍在SQL Server中处理数据库死锁异常时,可使用NOLOCK和READPAST。NOLOCK可能显示未提交事务的数据,READPAST会忽略被锁住的行。通过两个演示展示了它们的处理策略,还给出了开发实例,同时提醒要注意其局限性及业务逻辑的容忍度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所有Select加 With (NoLock)解决阻塞死锁,在查询语句中使用 NOLOCK 和 READPAST
处理一个数据库死锁的异常时候,其中一个建议就是使用 NOLOCK 或者 READPAST 。有关 NOLOCK 和 READPAST的一些技术知识点:
对于非银行等严格要求事务的行业,搜索记录中出现或者不出现某条记录,都是在可容忍范围内,所以碰到死锁,应该首先考虑,我们业务逻辑是否能容忍出现或者不出现某些记录,而不是寻求对双方都加锁条件下如何解锁的问题。
NOLOCK 和 READPAST 都是处理查询、插入、删除等操作时候,如何应对锁住的数据记录。但是这时候一定要注意NOLOCK 和 READPAST的局限性,确认你的业务逻辑可以容忍这些记录的出现或者不出现:
简单来说:

NOLOCK 可能把没有提交事务的数据也显示出来.
READPAST 会把被锁住的行不显示出来

不使用 NOLOCK 和 READPAST ,在 Select 操作时候则有可能报错误:事务(进程 ID **)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品

下面就来演示这个情况。
为了演示两个事务死锁的情况,我们下面的测试都需要在SQL Server Management Studio中打开两个查询窗口。保证事务不被干扰。

演示一 没有提交的事务,NOLOCK 和 READPAST处理的策略:
查询窗口一请执行如下脚本:

CREATE TABLE t1 (c1 int IDENTITY(1,1), c2 int)
go

BEGIN TRANSACTION
insert t1(c2) values(1)

在查询窗口一执行后,查询窗口二执行如下脚本:

select count(*) from t1 WITH(NOLOCK)
select count(*) from t1 WITH(READPAST)

结果与分析:

查询窗口二依次显示统计结果为: 1、0
查询窗口一的命令没有提交事务,所以 READPAST 不会计算没有提交事务的这一条记录,这一条被锁住了,READPAST 看不到;而NOLOCK则可以看到被锁住的这一条记录。

如果这时候我们在查询窗口二中执行:

select count(*) from t1 就会看到这个执行很久不能执行完毕,因为这个查询遇到了一个死锁。

清除掉这个测试环境,需要在查询窗口一中再执行如下语句:

ROLLBACK TRANSACTION
drop table t1

演示二:对被锁住的记录,NOLOCK 和 READPAST处理的策略


这个演示同样需要两个查询窗口。
请在查询窗口一中执行如下语句:

CREATE TABLE t2 (UserID int , NickName nvarchar(50))
go
insert t2(UserID,NickName) values(1,'郭红俊')
insert t2(UserID,NickName) values(2,'蝈蝈俊')
go

BEGIN TRANSACTION
update t2 set NickName = '蝈蝈俊.net' where UserID = 2

请在查询窗口二中执行如下脚本:

select * from t2 WITH(NOLOCK) where UserID = 2
select * from t2 WITH(READPAST) where UserID = 2

结果与分析:

查询窗口二中, NOLOCK 对应的查询结果中我们看到了修改后的记录,READPAST对应的查询结果中我们没有看到任何一条记录。 种情况下就可能发生脏读

清除测试环境方法参看演示一。

开发实例:

Code
public static string getUserNameByUserIDs(string UserID,string split)
       {
           UserID = "'" + UserID.Replace(split, "'" + split + "'") + "'";
           string sql = "select * from _user with(nolock) where userid in(" + UserID + ")";
           DataSet ds = Framework.IDE.DB.ExecuteDataSet(CommandType.Text, sql);
           string userNames = string.Empty;
           for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
            {
               if (i == 0)
                {
                   userNames = ds.Tables[0].Rows[i]["username"].ToString();
               }
                else
                {
                   userNames += split + ds.Tables[0].Rows[i]["username"].ToString();
               }
            }
           return userNames;
       }

<think>嗯,用户问的是如何预防和解决程序中的死锁问题。我需要先回顾一下死锁的基本概念,然后根据之前学过的引用内容来整理方法。记得用户提到了Java线程死锁和MySQL数据库中的死锁,所以可能需要分别讨论这两种情况。 首先,死锁的四个必要条件:互斥、持有并等待、不可抢占、循环等待。预防的话,通常就是打破这四个条件中的一个或多个。比如,资源一次性分配可以避免持有并等待,或者设定超时来破坏不可抢占的条件。 在Java线程中,可能需要按顺序获取锁,或者使用tryLock避免无限等待。数据库方面,引用里提到了一致的访问顺序和锁超时设置。这些都要整理进去。 用户需要的是方法和最佳实践,所以结构要清晰,分点列出。可能还要比较不同方法的优缺点,比如超时机制虽然有效但可能影响性能。另外,解决死锁的步骤比如检测和恢复也要说明。 需要确保回答涵盖程序层面和数据库层面,可能还要提到工具的使用,比如JStack或数据库的监控工具。最后,相关问题部分要生成几个有针对性的问题,帮助用户深入理解。</think>### 如何预防和解决程序中的死锁问题 #### 一、死锁的四大必要条件 死锁的发生需要同时满足以下四个条件: 1. **互斥条件**:资源一次只能被一个线程/进程占用 2. **持有并等待**:已持有资源的线程继续请求新资源时不会释放已有资源 3. **不可抢占**:资源只能由持有者主动释放,无法被强制剥夺 4. **循环等待**:多个线程形成环形资源等待链 $$ \text{死锁概率} \propto \frac{\text{并发量}^2}{\text{资源总数}} $$ (当并发量增加时,死锁概率呈平方级增长)[^1] #### 二、通用预防方法 1. **破坏持有并等待条件** - 原子化资源申请:使用`tryLockAll()`方法一次性获取所有所需资源 ```java // Java示例:通过ReentrantLock实现 if (lock1.tryLock() && lock2.tryLock()) { try { /* 临界区 */ } finally { lock2.unlock(); lock1.unlock(); } } ``` 2. **破坏不可抢占条件** - 设置锁超时机制: ```python # Python示例 with lock1.acquire(timeout=5): # 5秒超时 if lock1.locked(): with lock2.acquire(timeout=5): # 业务逻辑 ``` 3. **破坏循环等待条件** - 强制统一资源申请顺序:所有线程必须按预设顺序(如ID升序)申请资源 $$ \text{资源申请顺序} \Rightarrow R_1 < R_2 < \cdots < R_n $$ #### 三、数据库死锁专项解决方案 1. **预防措施** - 事务最小化:缩短事务执行时间 - 索引优化:通过`EXPLAIN`分析查询计划,减少锁竞争[^2] - 使用`SELECT ... FOR UPDATE NOWAIT`(Oracle)或`WITH (UPDLOCK, READPAST)`(SQL Server) 2. **检测恢复** - MySQL自动检测死锁并回滚代价较小的事务[^4] ```sql SHOW ENGINE INNODB STATUS; -- 查看最近死锁信息 ``` #### 四、程序级最佳实践 | 方法 | 优点 | 缺点 | |--------------------|---------------------|---------------------| | 锁顺序控制 | 完全预防循环等待 | 需要全局资源排序规范 | | 超时重试机制 | 实现简单 | 可能增加系统延迟 | | 资源预分配 | 避免运行时资源争用 | 可能导致资源利用率下降 | #### 五、解决步骤 1. 通过`jstack <pid>`(Java)或`SHOW PROCESSLIST`(MySQL)定位死锁 2. 分析线程/事务的锁持有情况 3. 根据具体场景选择: - 事务自动回滚(数据库) - 人工干预重启服务(应用程序) [^1]: 线程死锁的核心成因分析 [^2]: MySQL锁机制索引优化关联 [^4]: 数据库死锁检测工具使用方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值