我主要使用 javascript,对 sql Server 很陌生。我应该在表中插入两条记录。我在开发环境中做了尽职调查并且我的脚本有效,但是 dba 说要让它为生产做好准备,但我的 dba 人有一段时间不在办公室。那么如何为生产调整/附加以下数据脚本:是否有任何通用模板可供我使用?
use tpaApp
go
IF NOT EXISTS(SELECT 1 FROM AppVersion WITH (NOLOCK) WHERE key1 = 401 and key2 = 800)
INSERT INTO AppVersion (key1, key2, name, value, timestamp, changed, disabled, delete)
SELECT 401, 800, 'AndroidVersion', '1.0.1', GETDATE(), 0, 0, 0
IF NOT EXISTS(SELECT 1 From AppVersion WITH (NOLOCK) WHERE key1 = 401 and key2 = 900)
INSERT INTO AppVersion (key1, key2, name, value, timestamp, changed, disabled, delete)
SELECT 401, 900, 'IosVersion', '1.0.1', GETDATE(), 0, 0, 0
--回滚脚本:
IF EXISTS(SELECT 1 FROM AppVersion WITH (NOLOCK) WHERE key1 = 401 AND key2 = 800)
DELETE FROM AppVersion WHERE key1=401 and key2 =800
IF EXISTS(SELECT 1 FROM AppVersion WITH (NOLOCK) WHERE key1 = 401 AND key2 = 900)
DELETE FROM AppVersion WHERE key1=401 and key2 =900
最佳答案
精简版
另一方面,发布的查询存在严重缺陷,可能导致数据错误。您可以在不占用过多锁的单个安全语句中插入所有行:
INSERT INTO AppVersion (key1, key2, name, value, timestamp, changed, disabled, [delete])
SELECT
newrows.key1, newrows.key2, newrows.name, newrows.value,
newrows.timestamp, newrows.changed, newrows.disabled, newrows.[delete]
FROM ( VALUES
(401, 800, 'AndroidVersion', '1.0.1', GETDATE(), 0, 0, 0),
(401, 900, 'IosVersion', '1.0.1', GETDATE(), 0, 0, 0)
) newrows (key1, key2, name, value, timestamp, changed, disabled, [delete])
LEFT OUTER JOIN AppVersion
ON newrows.key1=AAppversion.key1 and newrows.key2=Appversion.key2
WHERE Appversion.key1 is null and #Appversion.key2 is null
解释
首先,这些查询表现出几个严重的问题。 NOLOCK
提示和使用 DELETE
而不是事务的尝试非常强烈地表明存在阻塞问题。试图掩盖它们虽然无济于事,事实上它会让事情变得更糟。例如,NOLOCK
并不意味着不要锁
。这意味着不要尊重锁,即读取脏数据时自己使用过多的锁
。这意味着如果其他一些事务插入相同的数据并将其删除,您的查询可能仍会看到它们。
我怀疑缺少索引或编写错误的查询导致阻塞。也许长期运行的事务最终会相互锁定?请记住,SQL Server 是最快的数据库之一。如果没有这些技巧,它每小时可以处理数 TB 数据的数百万笔交易。。这些问题应该得到解决,而不是被掩盖。例如,缺少索引意味着 SELECT 查询在尝试查找符合其条件的行时可能必须锁定很多行。使用适当的索引,它可能只需要锁定 1 行。
INSERT .. SELECT
用于插入查询结果,而不是特定值。要插入特定值,您应该使用 VALUES
子句。您可以在单个语句中添加多行,例如:
INSERT INTO AppVersion (key1, key2, name, value, timestamp, changed, disabled, delete)
VALUES
(401, 800, 'AndroidVersion', '1.0.1', GETDATE(), 0, 0, 0),
(401, 900, 'IosVersion', '1.0.1', GETDATE(), 0, 0, 0)
单个INSERT、DELETE、UPDATE 语句是原子的,即如果它们失败,它们将自动回滚。他们不需要需要明确的交易。在任何情况下,删除行都不与回滚相同。如果存在严重问题,则可能永远不会调用 DELETE
子句,从而在数据库中留下幽灵条目。
如果您只想插入新条目,您可以将值与目标表左连接并仅插入新条目。如果您要比较源表和目标表,您可以这样写:
INSERT into Target (ID,a,b,c)
SELECT source.ID,source.a,source.b,source.c
FROM source
LEFT OUTER JOIN target on source.ID = target.ID
where target.ID is null
这将只选择没有匹配目标键的源行并插入它们。
您也可以将值视为表格,方法是将它们括在括号中并提供表格和列名,例如:
FROM ( VALUES
(401, 800, 'AndroidVersion', '1.0.1', GETDATE(), 0, 0, 0),
(401, 900, 'IosVersion', '1.0.1', GETDATE(), 0, 0, 0)
) newrows (key1, key2, name, value, timestamp, changed, disabled, [delete])
这样你就可以只插入新行:
INSERT INTO AppVersion (key1, key2, name, value, timestamp, changed, disabled, [delete])
SELECT
newrows.key1, newrows.key2, newrows.name, newrows.value,
newrows.timestamp, newrows.changed, newrows.disabled, newrows.[delete]
FROM ( VALUES
(401, 800, 'AndroidVersion', '1.0.1', GETDATE(), 0, 0, 0),
(401, 900, 'IosVersion', '1.0.1', GETDATE(), 0, 0, 0)
) newrows (key1, key2, name, value, timestamp, changed, disabled, [delete])
LEFT OUTER JOIN AppVersion
ON newrows.key1=AAppversion.key1 and newrows.key2=Appversion.key2
WHERE Appversion.key1 is null and #Appversion.key2 is null
减少阻塞
至于隐含的阻塞问题,您应该消除长时间运行的事务和不必要的查询,删除 NOLOCK 并确保所有表都有正确的索引。如果 AppVersion
具有以 key1
和 key2
列开头的主键,或者如果有以它们开头的索引,则锁定将是最小的.
另一个考虑是使用 Snapshot isolation ,尤其是数据库级别的 READ_COMMITTED_SNAPSHOT
。这将允许读者读取现有数据,即使作者开始修改它们也是如此。服务器将在写入者更改旧行时开始复制它们,以确保读取者仍然可以读取数据而不会阻塞写入者。
在某种程度上,这就是您尝试(但失败了)使用 NOLOCK
提示进行的操作。
关于sql - 用于插入脚本的生产就绪模板,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50518430/