关于With (NoLock) https://www.cnblogs.com/kerrycode/p/3946268.html 测试参考

    所有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对应的查询结果中我们没有看到任何一条记录。 这种情况下就可能发生脏读
    =====================================================================================================================================
    访问频率比较高的app接口,在后台写的异常日志会偶尔出现以下错误。
    事务(进程 ID 64)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务

    实所有的死锁最深层的原因就是一个:资源竞争
    表现一:
    一个用户A 访问表A(锁住了表A),然后又访问表B

    另一个用户B 访问表B(锁住了表B),然后企图访问表A

    这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B,才能继续,好了他老人家就只好老老实实在这等了

    同样用户B要等用户A释放表A才能继续这就死锁了

    解决方法:
    这种死锁是由于你的程序的BUG产生的,除了调整你的程序的逻辑别无他法
    仔细分析你程序的逻辑,
    1:尽量避免同时锁定两个资源
    2: 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源.

    表现二:
    用户A读一条纪录,然后修改该条纪录
    这是用户B修改该条纪录
    这里用户A的事务里锁的性质由共享锁企图上升到独占锁(for update),而用户B里的独占锁由于A有共享锁存在所以必须等A释
    放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。
    这种死锁比较隐蔽,但其实在稍大点的项目中经常发生。
    解决方法:
    让用户A的事务(即先读后写类型的操作),在select 时就是用Update lock
    语法如下:
    select from table1 (updlock) where ….
    ==========================
    在联机事务处理(OLTP)的数据库应用系统中,多用户、多任务的并发性是系统最重要的技术指标之一。为了提高并发性,目前大部分RDBMS都采用加锁技术。然而由于现实环境的复杂性,使用加锁技术又不可避免地产生了死锁问题。因此如何合理有效地使用加锁技术,最小化死锁是开发联机事务处理系统的关键。

    死锁产生的原因
    在联机事务处理系统中,造成死机主要有两方面原因。一方面,由于多用户、多任务的并发性和事务的完整性要求,当多个事务处理对多个资源同时访问时,若双方已锁定一部分资源但也都需要对方已锁定的资源时,无法在有限的时间内完全获得所需的资源,就会处于无限的等待状态,从而造成其对资源需求的死锁。

    另一方面,数据库本身加锁机制的实现方法不同,各数据库系统也会产生其特殊的死锁情况。如在Sybase SQL Server 11中,最小锁为2K一页的加锁方法,而非行级锁。如果某张表的记录数少且记录的长度较短(即记录密度高,如应用系统中的系统配置表或系统参数表就属于此类表),被访问的频率高,就容易在该页上产生死锁。

    几种死锁情况及解决方法
    清算应用系统中,容易发生死锁的几种情况如下:
    ● 不同的存储过程、触发器、动态SQL语句段按照不同的顺序同时访问多张表;

    ● 在交换期间添加记录频繁的表,但在该表上使用了非群集索引(non-clustered);

    ● 表中的记录少,且单条记录较短,被访问的频率较高;

    ● 整张表被访问的频率高(如代码对照表的查询等)。
    以上死锁情况的对应处理方法如下:
    ● 在系统实现时应规定所有存储过程、触发器、动态SQL语句段中,对多张表的操作总是使用同一顺序。如:有两个存储过程proc1、proc2,都需要访问三张表zltab、z2tab和z3tab,如果proc1按照zltab、z2tab和z3tab的顺序进行访问,那么,proc2也应该按照以上顺序访问这三张表。

    ● 对在交换期间添加记录频繁的表,使用群集索引(clustered),以减少多个用户添加记录到该表的最后一页上,在表尾产生热点,造成死锁。这类表多为往来账的流水表,其特点是在交换期间需要在表尾追加大量的记录,并且对已添加的记录不做或较少做删除操作。

    ● 对单张表中记录数不太多,且在交换期间select或updata较频繁的表可使用设置每页最大行的办法,减少数据在表中存放的密度,模拟行级锁,减少在该表上死锁情况的发生。这类表多为信息繁杂且记录条数少的表。

    如:系统配置表或系统参数表。在定义该表时添加如下语句:
    with max_rows_per_page=1
    ● 在存储过程、触发器、动态SQL语句段中,若对某些整张表select操作较频繁,则可能在该表上与其他访问该表的用户产生死锁。对于检查账号是否存在,但被检查的字段在检查期间不会被更新等非关键语句,可以采用在select命令中使用at isolation read uncommitted子句的方法解决。
    该方法实际上降低了select语句对整张表的锁级别,提高了其他用户对该表操作的并发性。在系统高负荷运行时,该方法的效果尤为显著。

    例如:
    select
    from titles at isolation read uncommitted
    ● 对流水号一类的顺序数生成器字段,可以先执行updata流水号字段+1,然后再执行select获取流水号的方法进行操作。