提示:这里所摘抄的关于锁的知识有的是不同sql server版本的,对应于特定版本时会有问题。
当多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:
◆丢失更新
A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统
◆脏读
A用户修改了数据,随后B用户又读出该数据,但A用户因为某些原因取消了对数据的修改,数据恢复原值,此时B得到的数据就与数据库内的数据产生了不一致
◆不可重复读
A用户读取数据,随后B用户读出该数据并修改,此时A用户再读取数据时发现前后两次的值不一致
◆锁的类别有两种分法:
1. 从数据库系统的角度来看:分为独占锁(即排它锁),共享锁和更新锁
MS-SQL Server 使用以下资源锁模式。
锁模式 描述
共享 (S) 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。
更新 (U) 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。
排它 (X) 用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
意向锁 用于建立锁的层次结构。意向锁的类型为:意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。
架构锁 在执行依赖于表架构的操作时使用。架构锁的类型为:架构修改 (Sch-M) 和架构稳定性 (Sch-S)。
大容量更新 (BU) 向表中大容量复制数据并指定了 TABLOCK 提示时使用。
◆共享锁
共享 (S) 锁允许并发事务读取 (SELECT) 一个资源。资源上存在共享 (S) 锁时,任何其它事务都不能修改数据。一旦已经读取数据,便立即释放资源上的共享 (S) 锁,除非将事务隔离级别设置为可重复读或更高级别,或者在事务生存周期内用锁定提示保留共享 (S) 锁。
◆更新锁
更新 (U) 锁可以防止通常形式的死锁。一般更新模式由一个事务组成,此事务读取记录,获取资源(页或行)的共享 (S) 锁,然后修改行,此操作要求锁转换为排它 (X) 锁。如果两个事务获得了资源上的共享模式锁,然后试图同时更新数据,则一个事务尝试将锁转换为排它 (X) 锁。共享模式到排它锁的转换必须等待一段时间,因为一个事务的排它锁与其它事务的共享模式锁不兼容;发生锁等待。第二个事务试图获取排它 (X) 锁以进行更新。由于两个事务都要转换为排它 (X) 锁,并且每个事务都等待另一个事务释放共享模式锁,因此发生死锁。
若要避免这种潜在的死锁问题,请使用更新 (U) 锁。一次只有一个事务可以获得资源的更新 (U) 锁。如果事务修改资源,则更新 (U) 锁转换为排它 (X) 锁。否则,锁转换为共享锁。
◆排它锁
排它 (X) 锁可以防止并发事务对资源进行访问。其它事务不能读取或修改排它 (X) 锁锁定的数据
◆意向锁
意向锁表示 SQL Server 需要在层次结构中的某些底层资源上获取共享 (S) 锁或排它 (X) 锁。例如,放置在表级的共享意向锁表示事务打算在表中的页或行上放置共享 (S) 锁。在表级设置意向锁可防止另一个事务随后在包含那一页的表上获取排它 (X) 锁。意向锁可以提高性能,因为 SQL Server 仅在表级检查意向锁来确定事务是否可以安全地获取该表上的锁。而无须检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。
意向锁包括意向共享 (IS)、意向排它 (IX) 以及与意向排它共享 (SIX)。
锁模式 描述
意向共享 (IS) 通过在各资源上放置 S 锁,表明事务的意向是读取层次结构中的部分(而不是全部)底层资源。
意向排它 (IX) 通过在各资源上放置 X 锁,表明事务的意向是修改层次结构中的部分(而不是全部)底层资源。IX 是 IS 的超集。
与意向排它共享 (SIX) 通过在各资源上放置 IX 锁,表明事务的意向是读取层次结构中的全部底层资源并修改部分(而不是全部)底层资源。允许顶层资源上的并发 IS 锁。例如,表的 SIX 锁在表上放置一个 SIX 锁(允许并发 IS 锁),在当前所修改页上放置 IX 锁(在已修改行上放置 X 锁)。虽然每个资源在一段时间内只能有一个 SIX 锁,以防止其它事务对资源进行更新,但是其它事务可以通过获取表级的 IS 锁来读取层次结构中的底层资源。
◆独占锁:
只允许进行锁定操作的程序使用,其他任何对他的操作均不会被接受。执行数据更新命令时,SQL Server会自动使用独占锁。当对象上有其他锁存在时,无法对其加独占锁。
共享锁:共享锁锁定的资源可以被其他用户读取,但其他用户无法修改它,在执行Select时,SQL Server会对对象加共享锁。
◆更新锁:
当SQL Server准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。等到SQL Server确定要进行更新数据操作时,他会自动将更新锁换为独占锁,当对象上有其他锁存在时,无法对其加更新锁。
2. 从程序员的角度看:分为乐观锁和悲观锁。
◆乐观锁:完全依靠数据库来管理锁的工作。
◆悲观锁:程序员自己管理数据或对象上的锁处理。
MS-SQLSERVER 使用锁在多个同时在数据库内执行修改的用户间实现悲观并发控制
锁粒度是被封锁目标的大小,封锁粒度小则并发性高,但开销大,封锁粒度大则并发性低但开销小
SQL Server支持的锁粒度可以分为为行、页、键、键范围、索引、表或数据库获取锁
资源 描述
RID 行标识符。用于单独锁定表中的一行。
键 索引中的行锁。用于保护可串行事务中的键范围。
页 8 千字节 (KB) 的数据页或索引页。
扩展盘区 相邻的八个数据页或索引页构成的一组。
表 包括所有数据和索引在内的整个表。
DB 数据库。
SQL Server 提供以下的锁级别:DATABASE -- 无论何时当一个SQL Server 进程正在使用除master以外的数据库时,Lock Manager为该进程授予数据库级的锁。数据库级的锁总是共享锁,用于跟踪何时数据库在使用中,以防其他进程删除该数据库,将数据库置为脱机,或者恢复数据库。注意,由于master和tempdb数据库不能被删除或置为脱机,所以不需要在它们之上加锁。FILE -- 文件级的锁用于锁定数据库文件。EXTENT -- Extent锁用于锁定extents,通常仅在空间分配和重新分配的时候使用。一个extent由8个连续的数据页或索引页组成。Extent锁可以是共享锁也可以是独占锁。ALLOCATION_UNIT -- 使用在数据库分配单元上。TABLE -- 这种级别的锁将锁定整个表,包括数据和索引。何时将获得表级锁的例子包括在Serializable隔离级别下从包含大量数据的表中选取所有的行,以及在表上执行不带过滤条件的update或delete。Heap or B-Tree (HOBT) -- 用于堆数据页,或者索引的二叉树结构。PAGE -- 使用页级锁,由8KB数据或者索引信息组成的整个页被锁定。当需要读取一页的所有行或者需要执行页级别的维护如页拆分后更新页指针时,将会获取页级锁。Row ID (RID) -- 使用RID锁,页内的单一行被锁定。无论何时当提供最大化的资源并发性访问是有效并且可能时,将获得RID锁。KEY -- SQL Server使用两种类型的Key锁。其中一个的使用取决于当前会话的锁隔离级别。对于运行于Read Committed 或者 Repeatable Read 隔离模式下的事务,SQL Server 锁定与被访问的行相关联的的实际索引key。(如果是表的聚集索引,数据行位于索引的叶级。行上在这些你看到的是Key锁而不是行级锁。)若在Serializable隔离模式下,通过锁定一定范围的key值从而不允许新的行插入到该范围内,SQL Server防止了“幻读”。这些锁因而被称作“key-range lock”。METADATA -- 用于锁定系统目录信息(元数据)。APPLICATION -- 允许用户定义他们自己的锁,指定资源名称、锁模式、所有者、timeout间隔。
1. HOLDLOCK: 在该表上保持共享锁,直到整个事务结束,而不是在语句执行完立即释放所添加的锁。 2. NOLOCK:不添加共享锁和排它锁,当这个选项生效后,可能读到未提交读的数据或“脏数据”,这个选项仅仅应用于SELECT语句。 3. PAGLOCK:指定添加页锁(否则通常可能添加表锁)。 4. READCOMMITTED用与运行在提交读隔离级别的事务相同的锁语义执行扫描。默认情况下,SQL Server 2000 在此隔离级别上操作。 5. READPAST: 跳过已经加锁的数据行,这个选项将使事务读取数据时跳过那些已经被其他事务锁定的数据行,而不是阻塞直到其他事务释放锁,READPAST仅仅应用于READ COMMITTED隔离性级别下事务操作中的SELECT语句操作。 6. READUNCOMMITTED:等同于NOLOCK。 7. REPEATABLEREAD:设置事务为可重复读隔离性级别。 8. ROWLOCK:使用行级锁,而不使用粒度更粗的页级锁和表级锁。 9. SERIALIZABLE:用与运行在可串行读隔离级别的事务相同的锁语义执行扫描。等同于 HOLDLOCK。 10. TABLOCK:指定使用表级锁,而不是使用行级或页面级的锁,SQL Server在该语句执行完后释放这个锁,而如果同时指定了HOLDLOCK,该锁一直保持到这个事务结束。 11. TABLOCKX:指定在表上使用排它锁,这个锁可以阻止其他事务读或更新这个表的数据,直到这个语句或整个事务结束。 12. UPDLOCK :指定在读表中数据时设置更新 锁(update lock)而不是设置共享锁,该锁一直保持到这个语句或整个事务结束,使用UPDLOCK的作用是允许用户先读取数据(而且不阻塞其他用户读数据),并且保证在后来再更新数据时,这一段时间内这些数据没有被其他用户修改。
用于保护读取操作的共享锁的保持时间取决于事务隔离级别。采用 READ COMMITTED 的默认事务隔离级别时,只在读取页的期间内控制共享锁。在扫描中,直到在扫描内的下一页上获取锁时才释放锁。如果指定 HOLDLOCK 提示或者将事务隔离级别设置为 REPEATABLE READ 或 SERIALIZABLE,则直到事务结束才释放锁。
根据为游标设置的并发选项,游标可以获取共享模式的滚动锁以保护提取。当需要滚动锁时,直到下一次提取或关闭游标(以先发生者为准)时才释放滚动锁。但是,如果指定 HOLDLOCK,则直到事务结束才释放滚动锁。
用于保护更新的排它锁将直到事务结束才释放。
如果一个连接试图获取一个锁,而该锁与另一个连接所控制的锁冲突,则试图获取锁的连接将一直阻塞到:
将冲突锁释放而且连接获取了所请求的锁。
◆处理死锁和设置死锁优先级
死锁就是多个用户申请不同封锁,由于申请者均拥有一部分封锁权而又等待其他用户拥有的部分封锁而引起的无休止的等待
可以使用SET DEADLOCK_PRIORITY控制在发生死锁情况时会话的反应方式。如果两个进程都锁定数据,并且直到其它进程释放自己的锁时,每个进程才能释放自己的锁,即发生死锁情况。
@@LOCK_TIMEOUT 返回当前会话的当前锁超时设置,单位为毫秒
SET LOCK_TIMEOUT 设置允许应用程序设置语句等待阻塞资源的最长时间。当语句等待的时间大于 LOCK_TIMEOUT 设置时,系统将自动取消阻塞的语句,并给应用程序返回"已超过了锁请求超时时段"的 1222 号错误信息
示例
下例将锁超时期限设置为 1,800 毫秒。
SET LOCK_TIMEOUT 1800
◆设置事务隔离级别。
◆对 SELECT、INSERT、UPDATE 和 DELETE 语句使用表级锁定提示。
◆配置索引的锁定粒度
可以使用 sp_indexoption 系统存储过程来设置用于索引的锁定粒度
1 执行 EXEC SP_LOCK 报告有关锁的信息
2 查询分析器中按Ctrl+2可以看到锁的信息
如何避免死锁,最小化锁竞争
3 优化程序,检查并避免死锁现象出现;
4 .对所有的脚本和SP都要仔细测试,在正是版本之前。
5 所有的SP都要有错误处理(通过@error)
6 一般不要修改SQL SERVER事务的默认级别。不推荐强行加锁
7 将组成事务的语句作为一个的单独的批命令处理,以消除 BEGIN TRAN 和 COMMIT TRAN 语句之间的网络延迟造成的不必要的延迟。
8 考虑完全地使用存储过程编写事务代码。典型地,存储过程比批命令运行更快。
9 在游标中尽可早地Commit更新。因为游标处理比面向集合的处理慢得多,因此导致锁被持有的时间更久。 10 使用每个进程所需的最低级别的锁隔离。比如说,如果脏读是可接受的并且不要求结果必须精确,那么可以考虑使用事务隔离级别0(Read Uncommitted),仅在绝对必要时才使用Repeatable Read or Serializable隔离级别。 11 在 BEGIN TRAN 和 COMMIT TRAN 语句之间,绝不允许用户交互,因为这样做可能锁被持有无限期的时间。
1 如何锁一个表的某一行
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT * FROM table ROWLOCK WHERE id = 1
2 锁定数据库的一个表
SELECT * FROM table WITH (HOLDLOCK)
加锁语句:
sybase: update 表 set col1=col1 where 1=0 ; MSSQL: select col1 from 表 (tablockx) where 1=0 ; oracle: LOCK TABLE 表 IN EXCLUSIVE MODE ; 加锁后其它人不可操作,直到加锁用户解锁,用commit或rollback解锁
◆排它锁
新建两个连接,在第一个连接中执行以下语句
若同时执行上述两个语句,则select查询必须等待update执行完毕才能执行即要等待30秒
◆共享锁
在第一个连接中执行以下语句
◆共享锁
在第一个连接中执行以下语句
在第二个连接中执行以下语句
若同时执行上述两个语句,则第二个连接中的select查询可以执行
而update必须等待第一个事务释放共享锁转为排它锁后才能执行 即要等待30秒
◆死锁
同时执行,系统会检测出死锁,并中止进程
应用程序锁就是客户端代码生成的锁,而不是sql server本身生成的锁
处理应用程序锁的两个过程
sp_getapplock 锁定应用程序资源
sp_releaseapplock 为应用程序资源解锁
注意: 锁定数据库的一个表的区别
SELECT * FROM table WITH (HOLDLOCK) 其他事务可以读取表,但不能更新删除
SELECT * FROM table WITH (TABLOCKX) 其他事务不能读取表,更新和删除
交读事务使用行版本控制。 使用快照隔离。 使用绑定连接。
这个工具我想大家都明白,它的监视能力真的是无所不能。。。锁的痉挛状态也全在它的掌握之中。
1. 首先我做一个Person表,Name字段设定4000字节,这样一个数据页可以容纳2条数据,如下图:
2. 下面我们看看数据在数据页的分布情况。
3. 然后我们开启Profile,在“事件选择”的Events中选择”Lock:Acquired“和”Lock:Released“ ,然后运行,如下图:
使用测试数据
从图中可以看到,select执行的大概步骤如下:
第一步:给表(Object)加上IS(意向共享锁)。
第二步:先给1:78号数据页加IS锁,扫描78号数据页,然后释放IS锁。
第三步:同样的道理扫描之后的数据页。
第四步:最后释放表的IS锁,结束整个锁流程。
看完上面的一系列的Lock:Acquired 和 Lock:Released的话,你有没有发现一个问题,不是说好给记录(RID)加上S锁么???这里没加,是因为引擎进入78号数据页的时候,未发现它存在IU锁或者IX锁。。。所以。。。这个属于锁的组合,后续会说。
第一步: 给表(Object)加上IX锁,
第二步: 给数据页(1:78)数据页分配IU锁。然后开始逐一扫描78号数据页的RID记录,进入前就Acquired,退出后就Released,当扫描完78号数据页的所有RID后,再释放78 号数据页的IU锁,进入下一个数据页。。。
第三步: 我们发现ID=3是在89号数据页上,当引擎扫到该RID之后,我们观察到89号的数据页由IU锁变成了IX锁,并且把1:89:0(slot为0的记录)由U锁变成X锁,变成X锁 后,就排斥了其他所有的锁,这时候就可以进行Update操作了。
第四步: 后面就继续90号数据页,步骤类似,第二步和第三步。
不知道细心的你有没有发现,在Released Object之前我们才释放1:89:0的X锁,然后释放89号数据页的IX锁,这说明什么???说明这个Update是贯穿于这个事务的,不像Select操作中,扫完一个数据页就释放一个数据页。
大概扫了一下上面的图,或许你感觉和Update操作大差不差,会扫描数据页中的每个记录并加上U锁。当在1:89:0槽位中找到了目标记录后,然后将U锁转化为X锁,具体可以参考Update。
上一篇我只是做了一个堆表让大家初步的认识到锁的痉挛状态,但是在现实世界上并没有这么简单的事情,起码我的表不会没有索引对吧,,,还有就是我的表一定会有很多的连接过来,10:1的读写,很多码农可能都会遇到类似神乎其神的死锁,卡住,读不出来,插不进入等等神仙的事情导致性能低下,这篇我们一起来探讨下。
一: 当select遇到性能低下的update会怎么样?
1. 还是使用原始的person表,插入6条数据,由于是4000字节,所以两条数据就是一个数据页,如下图:
2. 为了模拟性能低下的Update操作,我们开个显式事务来更新ID=4的记录,并且用profile看一下,如下图:
3. 然后我们开下另一个会话连接,读取ID=6的记录会是怎样?好奇吗?
从上面流程你是否看到,当扫描到89号数据页的slot1槽位的时候卡住了。。。我想你应该知道update正好已经给这条记录加上了X锁。。。如果你够细心,你还会发现,给S锁附加记录的条件是在当引擎发现记录所在的数据页已经附加上了IX锁的情况下,才给该号数据页下的每条记录附加S锁,对吧。。。好了,既然在Profile上面看不到了,我还是有其他办法来判断到底select语句现在处于什么状态。
仔细观察上图可以看到,当前有51和52号会话,51号在1:89:1槽位上使用了X锁并且没有释放,52号此时也进入了1:89:1中,并且想给该RowID附加S锁,但是你也知道S和X锁是排斥的,所以很无奈的一直保持等待状态。
二:使用索引或许可以帮你逃过一劫
当你看完上面的讲叙,是不是有点害怕???要是在生产环境下出现了这种情况,那我们是不是死的很惨???那接下来使用索引是不是真的可以帮我们躲过一劫呢?下面跟我一起看一看。
1. 新建索引index
2. 然后我们看下数据页的分布情况,可以看到下图中78,89,90是表数据页,93号为索引数据页。
3. 麻蛋的,继续执行上面的那个慢update
4. 激动人心的时刻来了,由于数据太少,所以我这里强制让引擎执行我创建的索引,看看结果怎样?
居然没卡住???现在是不是有一股强烈的好奇心来了,狗狗狗。。。马上开启profile,看看到底都发生了什么?
仔细看完这个图,是不是觉得很有意思呢???具体步骤如下:
第一步:给表(Object)加上IS锁。
第二步:因为要走索引,给93号索引数据页加上IS锁。
第三步:找到93号索引数据页的目标key,给这个key加上S锁,有人可能就会问了。。。这个key不就是6嘛,为什么这个key=(61005a25560e),你要是太好奇我可以告 诉你,年轻人说话不要太屌,每行索引记录都有一个散列值,这个值就是根据索引的几个字段散列出来的,好处就是防止你的索引长度过大,导致锁这个记录的 时候太耗费锁空间了。。。。如果你还是不太相信的话,我用DBCC给你看一看。
第四步:根据这个key直接跳到存放记录的90号数据页中,万幸的是update的记录刚好不在90号数据页中。。。。就这样躲过一劫了。。。然后select顺利的读取到了该 读的记录,最后释放相关的IS锁。
会关注要不要给select指定nolock,这其中也包括自己没遇到过大数据吧,也包括自己的能力有限,只知道有锁这个玩意,细说的话就啥也不知道了,后来才知道携程有个规则,就是很多业务产线所写的select都必须指定nolock,懂一点的人可能会说nolock可以提升性能,如果你这样说,确实是这样,因为数据库的锁是有96字节开销的,没了锁,也就没有你在profile中看到accquired和released痉挛了,当你看完我的事件之后,你可能会意识到,性能提升不是最关心的,最关心就是不要出现死锁,锁等待。。。好了,言归正传,下面我们看看到底在数据库中可以指定多少个锁???
一:到底可以指定多少个锁
这个问题有意思,我们不需要记,只要你装一个SQL Prompt,有了这个神器,你就知道到底有多少个?如下图:
粒度锁:PAGLOCK, TABLOCK, TABLOCKX, ROWLOCK, NOLOCK
模式锁:HOLDLOCK, UPDLOCK, XLOCK
接下来我从粒度锁说起:
1. NOLOCK
都说nolock是无锁模式的,那到底是怎样的无锁呢???到这篇为止,你应该知道,如果不加nolock,我们的表,数据页是附加IS锁的,那接下来我用profile看下两者有什么区别。
从上图中,你会看到加上nolock之后,object上面附加了Sch-S锁,这个锁叫做“架构稳定锁”,很简单就是sql编译时附加的一把锁,目的就是防止在编译时,有其他连接修改表结构,而这个锁只与Sch-M锁冲突,与其他锁都兼容,这说明什么?说明其他连接锁住了记录也没关系,我的nolock不跟他们打交道,这样的话,就可能会读到脏数据,不过没关系,携程的很多业务是容许脏数据的,毕竟比锁等待,死锁要强得多,再说nolock读到了其他连接未修改或者未提交的数据,这个概率也比较低,就算遇到了也没关系,一般不会招来客诉的,客人或许再刷下页面,数据或许就正确了,对不对。。。
这个还是比较见名识义的,就是附加在table上的锁,也就是表锁了,很恐怖的。。。下面我举个Update的例子,看看前后对比。
在上面你有没有看到,X锁已经附加到OBJECT上面去了。。。这样的话,其他连接就动不了这个Object了,只能等待。。。
3. PAGLOCK
从上面两个图中,你应该可以看到,原来附加到RID上面的U锁,由于PagLock的提升,现在要附加到Page上面了,这个就是所谓的数据页锁。
这两个我就不细说了,TABLOCKX就是直接附加在table上的X锁,你可以通过select看一下。
ROWLOCK的话,默认情况下就是ROWLOCK,比如默认的Update,你会发现RID上被附加的U锁,这个就是行锁。
这个锁还是蛮有意思的,它就是update锁,如果你select下,它会呈现update的锁痉挛效果。
6. XLOCK
知道了UPDLOCK锁,我想XLOCK你也应该明白了。。。它就是delete锁,即排他锁,我可以让select带上排他锁。
最后一个我也没闹明白,据说是让语句在整个事务中持有锁,然后我就用select和update调试一下。
在使用SQL时,大都会遇到这样的问题,你Update一条记录时,需要通过Select来检索出其值或条件,然后在通过这个值来执行修改操作。
但当以上操作放到多线程中并发处理时会出现问题:某线程select了一条记录但还没来得及update时,另一个线程仍然可能会进来select到同一条记录。
一般解决办法就是使用锁和事物的联合机制:
1. 把select放在事务中, 否则select完成, 锁就释放了
2. 要阻止另一个select , 则要手工加锁, select 默认是共享锁, select之间的共享锁是不冲突的, 所以, 如果只是共享锁, 即使锁没有释放, 另一个select一样可以下共享锁, 从而select出数据
所有Select加 With (NoLock)解决阻塞死锁,在查询语句中使用 NOLOCK 和 READPAST
处理一个数据库死锁的异常时候,其中一个建议就是使用 NOLOCK 或者 READPAST 。有关 NOLOCK 和 READPAST的一些技术知识点:
对于非银行等严格要求事务的行业,搜索记录中出现或者不出现某条记录,都是在可容忍范围内,所以碰到死锁,应该首先考虑,我们业务逻辑是否能容忍出现或者不出现某些记录,而不是寻求对双方都加锁条件下如何解锁的问题。
NOLOCK 和 READPAST 都是处理查询、插入、删除等操作时候,如何应对锁住的数据记录。但是这时候一定要注意NOLOCK 和 READPAST的局限性,确认你的业务逻辑可以容忍这些记录的出现或者不出现:
简单来说:
不使用 NOLOCK 和 READPAST ,在 Select 操作时候则有可能报错误:事务(进程 ID **)与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。
SELECT * FROM Table WITH(NOLOCK)
SELECT * FROM Table WITH(READPAST)
实际开始动手用代码说话吧!
SQLServer2012在查询分析器里面开两个连接
结论:“表锁”锁定对该表的Select、Update、Delete操作,但不影响对该表的Insert操作也不影响以主键Id为条件的Select,所以Select如果不想等待就要在Select后加With(Nolock),但这样会产生脏数据就是其他事务已更新但并没有提交的数据,如果该事务进行了RollBack则取出的数据就是错误的,所以好自己权衡利弊,一般情况下90%以上的Select都允许脏读,只有账户金额相关的不允许。
结论:“表锁”锁定对该表的Select、Update、Delete操作,但不影响对该表的Insert操作也不影响以主键Id为条件的Select
结论:“行锁+表锁” 锁定对该表的Select、Update、Delete操作,但不影响对该表的Insert操作也不影响以主键Id为条件的Select、Update、Delete
结论:“行锁+表锁” 锁定对该表的Select、Update、Delete操作,但不影响对该表的Insert操作也不影响以主键Id为条件的Select、Update、Delete,也不影响以索引列Name为条件的Update、Delete但不可以Select
结论:可以理解为在使用版本控制软件的时候A迁出了一个文件,并且8i将这个87文件锁定,B就无法再迁出该文件了,直到A迁入解锁后才能被其他人迁出。
结论:可以理解为同样在使用版本控制软件的时候A迁出了一个文件,B也可以迁出该文件,两个人都可以对此文件进行修改,其中一个人先进行提交的时候,版本并没有变化所以可以正常提交,另一个后提交的时候,发现版本增加不对称了,就提示冲突由用户来选择如何进行合并再重新进行提交。
在乐观锁和悲观锁之间进行选择的标准是:冲突的频率与严重性。如果冲突很少,或者冲突的后果不会很严重,那么通常情况下应该选择乐观锁,因为它能得到更好的并发性,而且更容易实现。但是,如果冲突的结果对于用户来说痛苦的,那么就需要使用悲观策略。
我认为如果同一张表的并发很高,但并发处理同一条数据的冲突几率很低,那就应该使用乐观锁,反之,如果同一张表的并发不高,但同时处理同一条数据的几率很高,就应该使用悲观锁。
开发人员喜欢在SQL脚本中使用WITH(NOLOCK), WITH(NOLOCK)其实是表提示(table_hint)中的一种。它等同于 READUNCOMMITTED 。 具体的功能作用如下所示(摘自MSDN):
3: 不能为通过插入、更新或删除操作修改过的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查询优化器忽略 FROM 子句中应用于 UPDATE 或 DELETE 语句的目标表的 READUNCOMMITTED 和 NOLOCK 提示。
功能与缺陷
使用WIHT(NOLOCK)有利也有弊,所以在决定使用之前,你一定需要了解清楚WITH(NOLOCK)的功能和缺陷,看其是否适合你的业务需求,不要觉得它能提升性能,稀里糊涂的就使用它。
1:使用WITH(NOLOCK)时查询不受其它排他锁阻塞
打开会话窗口1,执行下面脚本,不提交也不回滚事务,模拟事务真在执行过程当中
BEGIN TRAN
UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1;
--ROLLBACK
打开会话窗口2,执行下面脚本,你会发现执行结果一直查询不出来(其实才两条记录)。当前会话被阻塞了
SELECT * FROM TEST;
打开会话窗口3,执行下面脚本,查看阻塞情况,你会发现在会话2被会话1给阻塞了,会话2的等待类型为LCK_M_S:“当某任务正在等待获取共享锁时出现”
, AS DatabaseName
此时查看会话1(会话1的会话ID为53,执行脚本1前,可以用SELECT @@spid查看会话ID)的锁信息情况,你会发现表TEST(ObjId=1893581784)持有的锁信息如下所示
打开会话窗口4,执行下面脚本.你会发现查询结果很快就出来,会话4并不会被会话1阻塞。
SELECT * FROM TEST WITH(NOLOCK)
从上面模拟的这个小例子可以看出,正是由于加上WITH(NOLOCK)提示后,会话1中事务设置的排他锁不会阻碍当前事务读取锁定数据,所以会话4不会被阻塞,从而提升并发时查询性能。
2:WITH(NOLOCK) 不发布共享锁来阻止其他事务修改当前事务读取的数据,这个就不举例子了。
本质上WITH(NOLOCK)是通过减少锁和不受排它锁影响来减少阻塞,从而提高并发时的性能。所谓凡事有利也有弊,WITH(NOLOCK)在提升性能的同时,也会产生脏读现象。
如下所示,表TEST有两条记录,我准备更新OBJECT_ID=1的记录,此时事务既没有提交也没有回滚
BEGIN TRAN
UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1;
--ROLLBACK
此时另外一个会话使用WITH(NOLOCK)查到的记录为未提交的记录值
假如由于某种原因,该事务回滚了,那么我们读取到的OBJECT_ID=1的记录就是一条脏数据。
什么时候可以使用WITH(NOLOCK)? 什么时候不能使用WITH(NOLOCK),这个要视你系统业务情况,综合考虑性能情况与业务要求来决定是否使用WITH(NOLOCK), 例如涉及到金融或会计成本之类的系统,出现脏读那是要产生严重问题的。关键业务系统也要慎重考虑。大体来说一般有下面一些场景可以使用WITH(NOLOCK)
1: 基础数据表,这些表的数据很少变更。
2:历史数据表,这些表的数据很少变更。
3:业务允许脏读情况出现涉及的表。
4:数据量超大的表,出于性能考虑,而允许脏读。
另外一点就是不要滥用WITH(NOLOCK),我发现有个奇怪现象,很多开发知道WITH(NOLOCK),但是有不了解脏读,习惯性的使用WITH(NOLOCK)。
为了搞清楚WITH(NOLOCK)与NOLOCK的区别,我查了大量的资料,我们先看看下面三个SQL语句有啥区别
SELECT * FROM TEST NOLOCK
SELECT * FROM TEST (NOLOCK);
SELECT * FROM TEST WITH(NOLOCK);
上面的问题概括起来也就是说NOLOCK、(NOLOCK)、 WITH(NOLOCK)的区别:
1: NOLOCK这样的写法,其实NOLOCK其实只是别名的作用,而没有任何实质作用。所以不要粗心将(NOLOCK)写成NOLOCK
2.1 至于网上说WITH(NOLOCK)在SQL SERVER 2000不生效,我验证后发现完全是个谬论。
2.2 在使用链接服务器的SQL当中,(NOLOCK)不会生效,WITH(NOLOCK)才会生效。如下所示
消息 4122,级别 16,状态 1,第 1 行
Remote table-valued function calls are not allowed.
3.语法上有些许出入,如下所示
这种语法会报错
-Msg 156, Level 15, State 1, Line 1
-Incorrect syntax near the keyword 'AS'.
这种语法正常
可以全部改写为下面语法
很多人误以为使用了WITH(NOLOCK)后,数据库库不会产生任何锁。实质上,使用了WITH(NOLOCK)后,数据库依然对该表对象生成Sch-S(架构稳定性)锁以及DB类型的共享锁, 如下所示,可以在一个会话中查询一个大表,然后在另外一个会话中查看锁信息(也可以使用SQL Profile查看会话锁信息)
不使用WTIH(NOLOCK)
使用WITH(NOLOCK)
另外,使用WITH(NOLOCK)并不是说就不会被其它会话阻塞,依然可能会产生Schema Change Blocking
会话1:执行下面SQL语句,暂时不提交,模拟事务正在执行
BEGIN TRAN
ALTER TABLE TEST ADD Grade VARCHAR(10) ;
会话2:执行下面语句,你会发现会话被阻塞,截图如下所示。
SELECT * FROM TEST WITH(NOLOCK)
-----
本文引用了以下网址内容:
--- end ---
在Python中,线程锁(Thread Lock)是一种重要的同步机制,用于在多线程环境中保护共享资源,避免出现竞争条件(Race Conditioni)常用线程锁类型:互斥锁(Mutex Lock)读写锁信号量(Semaphore)事件锁(Event)条件锁(Condition Lock)重入锁(Reentrant Lock)一、互斥锁在同一时刻只允许一个线程访问共享资源。当一个线程获
es 的应用场景第一个,就是日志实时分析。由于分布式系统中,服务众多,加上定时任务、消息等组件。造成日志文件非常多,且容量都很大,所以人工去查看日志就非常困难。es可以进行日志实时分析,简化了日志查看功能。第二个,就是搜索功能。比如电商商品数量众多,且商品规格参数文本众多,所以利用es的全文本搜索功能,可以提高搜索的速度,减少搜索时间,提高用户的体验。第三个,就是时序数据分析。比如云服务的监控,物
ZooKeeper 八种典型应用场景详细介绍为进一步加强对 zk 的认识,理解 zk 的作用,下面再详细介绍一下 zk 在生产环境中的典型应用场景。1. 配置维护1.1 什么是配置维护分布式系统中,很多服务都是部署在集群中的,即多台服务器中部署着完全相同的应用,起着完全相同的作用。当然,集群中的这些服务器的配置文件是完全相同的。若集群中服务器的配置文件需要进行修改,那么我们就需要逐台修改
# Java 锁的应用场景## 引言在并发编程中,线程的安全性是一个重要的问题。当多个线程同时访问共享的资源时,可能会导致数据的不一致性或者错误的结果。为了解决这个问题,Java提供了锁(Lock)机制,用于控制对共享资源的访问。本文将介绍Java中锁的概念、应用场景以及代码示例。## 锁的概念锁(Lock)是一种同步机制,用于限制同时访问共享资源的线程数目。在Java中,锁有两种类型
# Java 读写锁的应用场景在多线程编程中,读写锁是一个非常重要的概念,特别是在需要对共享资源进行高并发读操作和低并发写操作的时候。Java提供了`ReentrantReadWriteLock`类来实现读写锁。本文将指导您如何在Java中实现读写锁的基本应用场景。## 流程概述下面是实现读写锁的基本流程:| 步骤 | 描述
在现代Java应用程序中,锁的使用是不可避免的,以确保在多线程环境中对共享资源的正确访问。在众多锁的实现中,自旋锁因其低延迟和适应短时间锁定的特性而受到了广泛的关注。本文将详细探讨“Java锁自旋锁的应用场景”,并通过多个结构化组件展示其性能与实现细节。首先,我们需要关注自旋锁的背景。自旋锁通常用于临界区,它允许线程在锁不可用时持续检查该锁的状态,而不是进行线程调度。这样,当锁在短时间内被释放
# Java可重入锁应用场景## 什么是可重入锁?在讨论可重入锁的应用场景之前,我们首先需要了解什么是可重入锁。可重入锁是指同一个线程可以多次获得同一个锁,而不会造成死锁的情况。这种机制允许线程在持有锁的情况下,再次请求和获得同一个锁,从而避免了死锁的发生。在Java中,ReentrantLock类实现了可重入锁的功能。它提供了与synchronized关键字类似的互斥访问机制,但具有
## Java中锁的应用场景### 1. 简介Java中的锁机制是多线程编程中一种重要的同步机制,用于保护共享资源的一致性和可见性。锁的主要作用是确保在同一时刻只有一个线程可以访问被保护的代码块或资源,从而避免了多线程并发访问导致的数据竞争和不一致性的问题。在本文中,我们将介绍Java中锁的应用场景以及如何使用不同类型的锁来实现线程的同步和互斥。### 2. 锁的应用场景在Ja
# Java悲观锁的应用场景在多线程编程中,由于多个线程可能同时读写共享资源,会导致数据不一致或者竞争条件的出现。为了确保数据的完整性与正确性,Java提供了多种锁机制,其中悲观锁是一种重要的锁策略。接下来,我们将通过示例来探讨悲观锁的应用场景。## 什么是悲观锁?悲观锁的基本理念是“我不相信任何人”,即在访问共享资源之前,先对其加锁,以防止其他线程的干扰。Java中的悲观锁通常是通过
在Java并发场景中,会涉及到各种各样的锁,比如:分段锁、公平锁,独享锁、共享锁、乐观锁,悲观锁等等,感觉特别的繁杂,一句话很难描述清楚,但又特别的重要。下面我就通过图文并茂的方式,一起来梳理和详解最全锁!——嘀嘀!上车了!准备上车了!!——乐观锁 VS 悲观锁乐观锁与悲观锁是一种广义上的概念,在Java并发编程和数据库中都有实际的应用场景。1.乐观锁顾名思义,就是很乐观,每次去拿数据的时候都认为
1. 两个比较的表数据差异非常大:TableDiff光生产差异脚本都要很久时间,效率能 高得起来吗? BCP将目的端的表数据重建,TableDiff搞不定; 两个比较的表中,存在不适宜比较的字段类型:这些类型要不就是本身很复杂(如:text类型),timestamp类型),第一种比较起来
互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景互斥锁与自旋锁:谁更轻松自如?读写锁:读和写还有优先级区分?乐观锁与悲观锁:做事的心态有何不同?总结 多线程访问共享资源的时候,避免不了资源竞争而导致数据错乱的问题,所以我们通常为了解决这一问题,都会在访问共享资源之前加锁。最常用的就是互斥锁,当然还有很多种不同的锁,比如自旋锁、读写锁、乐观锁等,不同种类的锁自然适用于不同的场景。如果选择了错误的
要用到多线程 ,就不得不考虑,线程之间的交互,线程是否安全4.1 OSSpinLock 自旋锁 :线程通过busy-wait-loop的方式来获取锁,任时刻只有一个线程能够获得锁,其他线程忙等待直到获得锁。spinlock在多处理器多线程环境的场景中有很广泛的使用,一般要求使用spinlock的临界区尽量简短,这样获取的锁可以尽快释放,以满足其他忙等的线程。Spinloc
开发者(KaiFaX) 面向全栈工程师的开发者 专注于前端、Java/Python/Go/PHP的技术社区 作者 | Vt正文-开门见山谈起redis锁,下面三个,算是出现最多的高频词汇:setnxredLockredissonsetnx其实目前通常所说的setnx命令,并非单指redis的 setnx key value这条命令。一般代指redis中对set命令加上nx参数进行使
前言数据库是一个允许多用户、多会话、多线程访问的系统。为了在并发访问中能保证数据的一致性和完整性,一般会使用事务来做控制,外加锁来作为辅助手段。所以今天整理了一下锁的相关知识,看看它在事务里是怎么配合使用的。锁的分类我们先来看下锁的分类及其作用有哪一些,以便能更好的分析运用场景。乐观/悲观锁乐观锁:在读取数据时会假设各个事务互不影响,它们会处理好属于自己的那部分数据。如果在更新数据时,发现有其他事
1,概论 事物这东西相信大家都不陌生吧,在学习Spring,Mybatis等框架中, 只要是涉及到数据存储和修改的,都会有事物的存在, 废话就不多说了下面我们来简单的介绍下Redis事物以及锁。2,Redis事物简介? Redis 事务可以一次执行多个命令, 并且我们来了解几个重要的知识点:开启事物后,一切命令都会放入队列当中,当执行EXEC以后才会挨
前言对于锁大家肯定不会陌生,比如 synchronized 关键字 和 ReentrantLock 可重入锁,一般我们用其在多线程环境中控制对资源的并发访问。但是随着业务的发展,分布式的概念逐渐出现在我们系统中,我们在开发的过程中经常需要进行多个系统之间的交互,于是上面的加锁方法就会失去作用。于是在分布式锁就自然而然的诞生了,接下来我们来聊一聊分布式锁实现的几种方式。分布式锁的使用场景效率性:使用
竞争锁是造成多线程应用程序性能瓶颈的主要原因 区分竞争锁和非竞争锁对性能的影响非常重要。如果一个锁自始至终只被一个线程使用,那么 JVM 有能力优化它带来的绝大部分损耗。如果一个锁被多个线程使用过,但是在任意时刻,都只有一个线程尝
本文不对自旋锁和互斥锁的概念做阐述,重点分析它们之间的区别和自旋锁的使用场景。自旋锁和互斥锁的区别a. 互斥锁加锁失败后,线程会释放 CPU,给其他线程;自旋锁加锁失败后,线程会忙等待,直到它拿到锁; b. 自旋锁会关闭本CPU内核抢占,互斥锁不会. 问:为什么自旋锁要关闭本CPU内核抢占? 答:加锁进程A获取一把自旋锁之后,开始执行临界区代码,此时发生了调度,执行进程B。恰巧进程B需要用到该自旋
继承(inheritance)本质是类设计层次的复用。继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,继承是类设计层次的复用。
摘要:本文深入解析了如何在HarmonyOS Next中利用NFCP2P技术实现跨设备文件传输。文章从权限配置、NFCP2P通信机制、文件系统操作到UI设计进行系统讲解,提供了完整的代码示例和性能优化建议。针对NFC传输限制,提出结合WLANP2P的混合传输方案,并对比了不同近场通信技术的特性。最后展望了HarmonyOS在智能路由和服务化传输方面的发展趋势,为开发者实现高效安全的文件传输功能提供了全面指导。
目录一、什么是AHB总线?二、信号描述2.1 全局信号2.2 主机信号2.3 从机信号2.4 译码器信号2.5 多路选择器信号三、信号传输3.1 基本传输3.2 传输类型声明 一、什么是AHB总线? AHB是一种适用于高性能可综合设计的总线接口。 最常见的AHB从属设备包括内部存储器、外部存储器接口 ...
在处理GB级日志文件或千万行CSV时,传统`readBytes()`一次性加载的方式常导致内存溢出(OOM)。Okio作为适用于Android、Java和Kotlin多平台的现代I/O库,通过**分段缓冲**和**按需读取**机制,可将内存占用控制在KB级别。本文将系统讲解如何利用Okio的`Buffer`、`BufferedSource`和`FileSystem`组件,实现高效的大数据集流式处理...
本文围绕NAS音乐管理展开,指出当前存在功能不聚焦、体验不友好、成本与效率矛盾三大困境。为此,提出Music Tag Web与音流组合的破局方案,两者功能互补、成本可控且技术适配。文章还详细介绍了该方案的部署步骤,包括前期准备、Music Tag Web部署、音流配置及功能优化,并提供了部署和使用阶段常见问题的解决方案,最后总结了选择该方案的三大理由:本土化优势、全流程覆盖、性价比高。