插入脚本的生产就绪模板

时间:2018-05-24 21:30:07

标签: sql sql-server database

我主要使用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

2 个答案:

答案 0 :(得分:0)

这是一个粗略的模板。但是,您的组织可能已制定了与此不同的规则和标准。

    --No point slowing things down by calling GETDATE() twice
    DECLARE @dtm DATETIME = GETDATE();

    --Remove the (NOLOCK ) you need to be sure the SELECT statement returns 100% accurate results.
    --Perform inside a single Transaction to ensure nothing changes between IF statement and INSERT statement
    BEGIN TRY
        BEGIN TRANSACTION
        IF NOT EXISTS(SELECT 1 FROM AppVersion WHERE key1 = 401 and key2 = 800)
                        INSERT INTO AppVersion (key1, key2, name, value, timestamp, changed, disabled, delete)
                        SELECT 401, 800, 'AndroidVersion', '1.0.1',@dtm, 0, 0, 0

        IF NOT EXISTS(SELECT 1 From AppVersion WHERE key1 = 401 and key2 = 900)
                        INSERT INTO AppVersion (key1, key2, name, value, timestamp, changed, disabled, delete)
                        SELECT 401, 900, 'IosVersion', '1.0.1', @dtm, 0, 0, 0
        COMMIT 
    END TRY
    BEGIN CATCH
      -- Determine if an error occurred.  
      IF @@TRANCOUNT > 0  
         ROLLBACK 

        --PUT YOUR ERROR HANDLING HERE  i.e Log Error

    END CATCH;  

答案 1 :(得分:0)

短版

另一方面,发布的查询存在严重缺陷,可能导致数据错误。您可以在不需要过多锁定的单个安全语句中插入所有行:

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并不代表don't take locks。这意味着don't respect locks, ie read dirty data while taking excessive locks yourself。这意味着如果某个其他事务插入相同的数据并将其删除,您的查询仍可能会看到它们。

我怀疑缺少索引或编写错误的查询会导致阻塞。也许长时间运行的交易最终会相互锁定?请记住,SQL Server是最快的数据库之一。它可以在没有这些技巧的情况下每小时处理数百万条数据。这些问题应该修复,而不是掩盖。例如,缺少索引意味着SELECT查询可能必须在尝试查找符合其条件的行时锁定 lot 行。使用适当的索引,它可能只需要锁定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子句,从而在数据库中保留ghost条目。

如果只想插入新条目,可以将值与目标表连接起来,只插入新的值。如果您要比较源表和目标表,请编写:

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具有以key1key2列开头的主键,或者如果存在以它们开头的索引,则锁定将是最小的。

另一个考虑因素是在数据库级别使用Snapshot isolation,尤其是READ_COMMITTED_SNAPSHOT。这将允许读者即使作者开始修改它们也能读取现有数据。当编写者更改旧行时,服务器将开始制作旧行的副本,确保读者仍然可以在不阻塞编写器的情况下读取数据。

在某种程度上,这是你尝试(但失败)与NOLOCK提示有关的内容。