考虑到处理缺点的方法,使用 NOLOCK 会出现哪些问题?

var*_*ble 7 sql-server concurrency isolation-level nolock

我正在研究使用 NOLOCK SQL 从一个活跃使用的数据库加载数据到报告数据库中的危害。我知道使用 NOLOCK 存在问题,但我正在考虑使用下面解释的策略来应对这些问题。

我知道有更好的方法,如日志传送、复制、镜像、AG、集群来拥有副本数据库,但这些不是这个问题的重点。

目标数据库有一个保存 LoadDate 的历史表。每 10 分钟,调度程序根据时间戳运行带有 WHERE 子句的 SELECT 查询(使用 NOLOCK)来获取数据,将其转储到临时表中,删除重复项(如果有)(保持最新)并将其合并到目标表中。

当 SELECT 查询运行时,SELECT 查询中使用的两个表可能会同时修改,但联接条件的列值不会更改。

伪代码示例:

DECLARE @LASTLOADDATE=SELECT MAX(LOADDATE) FROM HISTORYTABLE;
DECLARE @CURRENTDATE=GETDATE();

SELECT C1,C2,C3
FROM TBL1 T1 WITH(NOLOCK)
JOIN TBL2 T2 WITH(NOLOCK) ON T1.ID=T2.T1_ID
WHERE T1_TIMESTAMP>@LASTLOADDATE AND T1_TIMESTAMP<=@CURRENTDATE
//There is no index on timestamp column but an index may be added in the future

INSERT THE ABOVE RECORDS INTO STAGING TABLE
REMOVE DUPLICATES
MERGE DATA FROM STAGING TABLE INTO TARGET TABLE
INSERT INTO HISTORY TABLE THE VALUE FROM @CURRENTDATE
Run Code Online (Sandbox Code Playgroud)
  1. NOLOCK 会导致脏读。但我的数据库/应用程序中没有事务,所以这不是问题。

  2. NOLOCK 会导致不可重复读取和幻像读取,这也没关系,因为我的 SELECT 查询没有在事务内运行。

  3. 返回重复项(由于页面拆分和分配顺序扫描)不是问题,因为我暂存数据,按时间戳字段使用 row_number 分区来保留最新记录,然后与目标表执行 SQL MERGE。因此,分区和选择最新记录可以解决重复问题。或者,该记录将更新其时间戳(因为它是在 SELECT 运行后更新的),因此无论如何,后续调度程序运行都会获取最新值。

  4. 当 select 读取了某个点之后,在该点之前插入了一条记录(或由于更新而导致插入),则可能会发生丢失记录(由于分配顺序扫描)。当查询运行时,在这种情况下它将丢失记录。但由于记录有时间戳,因此它将在下次运行中被拾取。

  5. 仅当 NOLOCK 与 INSERT/UPDATE 一起使用时才会发生损坏问题,而我仅使用 SELECT,因此这不是问题。

我的解释是否有任何不正确的地方?还有其他我没有看到的问题吗?

Pau*_*ite 17

每当您读取可能被其他进程同时更改的数据时,您都必须决定需要什么级别的事务隔离。

\n

可序列化以下的每个隔离级别都需要权衡。由实施者决定哪个级别适合具体情况。即使您不使用显式事务,这也适用。即使单个SELECT语句也是一个事务

\n

这个问题表明你已经考虑过这些问题,这很好。

\n

使用我文章中的摘要关于读未提交隔离级别的文章中的摘要,特定于读未提交的主要不一致是:

\n
    \n
  • 脏读(遇到尚未提交、可能永远不会提交的数据)
  • \n
  • 包含已提交和未提交数据混合的行
  • \n
  • 由于按分配顺序扫描而丢失/重复的行(另请参阅此存档帖子
  • \n
  • 混合状态(“损坏”)单个(单列)LOB 值
  • \n
  • 错误 601 \xe2\x80\x93“由于数据移动,无法使用 NOLOCK 继续扫描”
  • \n
\n

这是除了锁定已提交读操作下可能出现的不一致之外的情况:

\n
    \n
  • 缺少先前提交的行
  • \n
  • 多次遇到提交的行
  • \n
  • 在单个语句/查询计划中遇到同一行的不同提交版本
  • \n
  • 同一行(但不同列)不同时间点提交的数据
  • \n
  • 提交的数据读取似乎与启用和检查的约束相矛盾
  • \n
\n

该问题没有包含足够的细节来评估所有这些。例如,我们不知道查询是否返回任何大型 LOB 数据类型,或者是否有任何数据可能存储在ROW_OVERFLOW分配单元中的行外。

\n

尽管如此,根据所提供的信息,似乎READ UNCOMMITTED 可能适合您的需求。考虑到重复数据删除、重新获取任何先前丢失的数据等,可能的不一致似乎是可以容忍的。

\n

如果可以添加然后通过回滚删除的数据(例如,错误条件),并且从不重新添加\xe2\x80\x94,或者如果计时很重要,则脏读可能会给您带来问题。

\n

只有您才能真正确定这些事情是否与您的应用程序有关。未提交读提供了 SQL Server 中可用的所有级别中最少的隔离保证,但这并不意味着它永远不是正确的选择。请记住,未来的风险是有人不尊重假设,使得未提交的读取现在可能被您接受。

\n

如果您的系统和工作负载可以承受所涉及的开销,那么 RCSI 和 SI 等行版本控制隔离级别是一种流行的替代方案。

\n
\n

直接解决您问题中的编号点:

\n
    \n
  1. 每个语句都在事务中运行。
  2. \n
  3. 您的选择正在自动提交事务中运行。
  4. \n
  5. 如果两次读取实际上在所有可能的情况下返回完全相同的重复行,那么这很好。
  6. \n
  7. 也可以,如果添加一行确实不重要,那么最终会添加一行。
  8. \n
  9. 您的选择(显然)不会“损坏”您正在读取的表,但是如果您读取“损坏”的数据然后将其存储在某个地方,效果是一样的吗?
  10. \n
\n