如何在不锁定表的情况下使用 SELECT?
将SELECT语句与正在进行的INSERT或UPDATE语句一起使用,对行或可能对整个表设置排他锁,直到操作的事务被提交或回滚。假设您正在处理一个包含数千行的非常大的表,并且数据库表的设计效率不高。请记住,在现实生活中,您并不是唯一一个使用数据库的人,该数据库可能会同时在许多用户的设备上被访问。因此,如果您正在使用 SELECT 语句读取表并且其他人正在尝试执行 INSERT 语句,则可能会发生锁定并且两个事务相互阻塞。
所以SQL Server 中的解决方案是NOLOCK表提示允许您指示查询优化器读取给定表,而无需获取独占或共享锁。
步骤 1:使用数据库创建数据库
询问:
CREATE DATABASE GFG_Demo;
USE GFG_Demo;
输出:
第 2 步:表定义
我们的 GFG_Demo 数据库中有以下演示表。
询问:
CREATE Table GFG_Demo_Table
( Order_date date, Sales int);
输出:
步骤 3:向表中添加数据
使用以下语句将数据添加到 GFG_Demo_Table。
询问:
INSERT INTO GFG_Demo_Table(Order_date,Sales)
VALUES('2021-01-01',20),('2021-03-02',32),('2021-02-03',45),
('2021-01-04',31),('2021-03-05',33),('2021-01-06',19),
('2021-01-01',20),('2021-03-02',32),
('2021-02-03',45),('2021-01-01',20),('2021-03-02',32),
('2021-02-03',45), ('2021-01-04',31),('2021-03-05',33),
('2021-01-06',19), ('2021-01-04',31),('2021-03-05',33),
('2021-01-06',19), ('2021-01-01',20),('2021-03-02',32),
('2021-02-03',45), ('2021-01-04',31),('2021-03-05',33),
('2021-01-06',19), ('2021-01-04',31),('2021-03-05',33),
('2021-01-06',19), ('2021-04-07',21),('2021-03-08',10),
('2021-02-09',40), ('2021-03-10',20),('2021-03-11',26),
('2021-04-12',22), ('2021-04-13',10),('2021-01-14',28),
('2021-03-15',15), ('2021-01-16',12),('2021-04-17',10),
('2021-02-18',18), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-03-22',51),('2021-02-23',13),
('2021-03-24',15), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-03-22',51),('2021-02-23',13),
('2021-03-24',15), ('2021-03-22',51),('2021-02-23',13),
('2021-03-24',15), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-03-22',51),('2021-02-23',13),
('2021-03-24',15), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-03-22',51),('2021-02-23',13),
('2021-03-24',15), ('2021-03-22',51),('2021-02-23',13),
('2021-03-24',15), ('2021-03-22',51),('2021-02-23',13),
('2021-03-24',15), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-02-25',30),('2021-03-26',14),
('2021-04-27',16), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-04-19',14),('2021-01-20',16),
('2021-02-21',12), ('2021-02-28',15),('2021-01-29',20),
('2021-01-30',18);
输出:
第 4 步:使用 NOLOCK
现在我们的表已经准备好了,让我们做一个测试,看看 NOLOCK 有什么用处。
询问:
BEGIN TRAN UPDATE GFG_Demo_Table
SET Sales= 25 WHERE Sales=20;
第 5 步: BEGIN TRAN语句将启动事务,该事务将启动将在 SQL 会话号52下的 GFG_Demo_Table 表上运行以下 UPDATE 语句的事务(在我们的系统中可能不同),而不通过提交或滚动完成事务回来了。
询问:
SELECT * FROM GFG_Demo_Table;
第 6 步:执行此查询时,您可能会注意到 SELECT 语句花费的时间比平时稍长。这种差异在较大的表中更为明显。试试这个,有数千行。这是因为事务仍未提交或回滚。这就是为什么它会阻止其他尝试从表中读取数据的查询。您可以使用以下命令进行检查。
询问:
sp_who2 52
注意:对于这个特定的表,SELECT语句不会阻塞很长时间,因为表不是很大,但是对于有数千条数据的表,您可以看到阻塞的查询的会话号通过运行上述命令选择语句。
第 7 步:要运行被阻止的查询,您必须终止或提交或回滚事务。然而,这不是最优选的解决方案。这就是WITH (NOLOCK)出现的地方。
询问:
SELECT * FROM GFG_Demo_Table WITH (NOLOCK);
现在,运行上面的查询,然后使用前面的命令检查 SELECT 语句的状态。
输出:
第 8 步: BlkBy列是空白的,这意味着现在已解除锁定。你也可以使用WITH (READUNCOMMITTED) ,它与 WITH (NOLOCK) 做同样的事情。它还可以读取未提交的数据,而无需等待 UPDATE 语句释放锁。
输出:
注意:您只能将这些表提示与SELECT语句一起使用,而不能与任何其他语句一起使用。
方法二:
您可以使用SET TRANSACTION ISOLATION LEVEL语句将连接级别的事务隔离级别更改为 READ UNCOMMITTED,而不是使用那些允许在查询级别进行脏读的表提示。
询问:
SET TRANSACTION ISOLATION LEVEL READ
UNCOMMITTED;
SELECT * FROM GFG_Demo_Table;
输出:
此查询还将直接检索相同的数据,而不使用任何表提示,也无需等待 UPDATE 语句释放它对表执行的锁定。
使用 NOLOCK 的缺点
- 一开始使用 NOLOCK 似乎是一个好主意,因为我们可以更快地获得请求的数据,而无需等待其他操作被提交。但是,脏读是一个大问题,我们得到的结果可能并不准确。因此,在联机事务处理 (OLTP) 环境中,自动添加 NOLOCK 提示通常不是一个好的做法。
- 添加 NOLOCK 提示或更改隔离级别确实会更改为单个查询或会话中的所有命令处理锁定的方式。彻底测试这些更改以检查它们是否满足您的需求。