【问题标题】:Best way to handle insert/update process for application with high transaction of data? [closed]处理具有高数据事务的应用程序的插入/更新过程的最佳方法? [关闭]
【发布时间】:2018-08-12 21:46:26
【问题描述】:

在过去的几个月里,我一直在研究我的单页应用程序,在尝试改进后端的一些东西的同时,我开始更深入地研究插入/更新和一般数据库的过程。我使用 SQL Server 2008 并且数据库是使用 Primary KeysForeign Keys 以及 Indexes 设计的,以便在表之间建立强大而可靠的连接。我说的是这些元素,因为我们当前的系统有一个设计很差的数据库。 deadlocks 有很多问题,而且性能很慢。我们的系统是全州范围的产品,具有大量数据交易,尤其是在乞讨或学年结束等时间段。然而,我的新应用程序在未来应该会有相同甚至更大的转换,因此我的数据库设计和插入/更新过程必须改进并防止死锁。我的问题是关于应该在这种系统中使用的 SQL Server 标准。例如,我最近开始更多地研究存储过程,并且似乎很多 DBA 专家都建议在反复使用相同查询的情况下采用这种方式。这应该可以防止一些安全风险并提高效率。以下是我目前如何在我的系统中处理 InsertUpdate 事务的示例:

<cftransaction action="begin">
    <cftry>
        <cfquery name="qrySaveDictionary" datasource="#dsn#">
            DECLARE @Status BIT = <cfqueryparam cfsqltype="cf_sql_bit" value="#trim(arguments.frm_status)#">;
            DECLARE @Name VARCHAR(50) = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#trim(arguments.frm_name)#">;
            DECLARE @Code CHAR(2) = UPPER(<cfqueryparam cfsqltype="cf_sql_char" maxlength="2" value="#trim(arguments.frm_code)#">);
            DECLARE @ActionDate DATETIME = CURRENT_TIMESTAMP;
            DECLARE @ActionID UNIQUEIDENTIFIER = <cfqueryparam cfsqltype="cf_sql_idstamp" value="#SESSION.AccountID#">;

            <cfif len(trim(arguments.frm_recordid))>
                DECLARE @RecordID INT = <cfqueryparam cfsqltype="cf_sql_integer" value="#trim(arguments.frm_recordid)#">;
                IF EXISTS(SELECT RecID FROM Dictionary WHERE RecID = @RecordID)
                BEGIN
                    UPDATE Dictionary
                    SET
                        Status = @Status,
                        Name = @Name,
                        Code = @Code,
                        ActionDt = @ActionDate,
                        ActionID = @ActionID
                    WHERE RecID = @RecordID
                    SELECT @RecordID AS RecID
                END
            <cfelse>
                BEGIN
                    IF NOT EXISTS(SELECT 1 FROM Dictionary WHERE Code = @Code)
                    INSERT INTO Dictionary (
                        Status,Name,Code,ActionDt,ActionID
                    ) VALUES (
                        @Status,@Name,@Code,@ActionDate,@ActionID
                    )
                    SELECT SCOPE_IDENTITY() AS RecID
                END
            </cfif>
        </cfquery>

        <cfset local.fnResults = {status : "200", message : "Record successully saved!", RecID : qrySaveDictionary.RecID}>

        <cfcatch type="any">
            <cftransaction action="rollback" />
                <cfset local.fnResults = {status : "400", class : "alert-danger", message : "Error! Please contact your administrator."}>
            </cfcatch>
        </cftry>
    </cftransaction>

在上面的示例中,我使用 ColdFusion 检查函数参数中传递的现有记录。此外,我在 ColdFusion 中使用事务来防止出现错误情况时出现错误数据。此外,我在查询中声明所有参数,然后运行 ​​Update 或 Insert 语句。我读了很多关于IF EXISTIF NOT EXIST 的博客,有人说它们不利于性能并且可能导致死锁。我的问题是这种陈述的最佳方法是什么:

1) 像这样:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

或者

2)

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran

或者

3) 也许使用MERGE,我没有找到一个很好的例子。另外我想知道这应该是一个存储过程函数吗?再次,我自己在这个项目上工作,我们公司没有 DBA,我的资源有限。我正在尝试在 Internet 上进行研究并获取有关 SQL Server 和良好实践的信息。正如我所提到的,这个项目对数据交易很重,对我来说选择正确的方法非常重要。如果有人可以提供一些想法,信息或示例,将不胜感激。

【问题讨论】:

  • 数据库的结构是什么?您能否解释一下为什么需要检查和更新数据是否存在?
  • 迄今为止最慢的方法是循环一个普通的 CFQUERY 标签,我强烈建议不要这样做。
  • 是的。这是另一种方式,将循环放在 CFQUERY 中,但是,正如我所说,我们对在 CFQUERY 之外循环的结果感到惊讶,但仅使用 EXECUTE SP。我会尝试挖掘代码。那是几年前的事了……
  • 这是我的看法。 MERGE 让 SQL Server 找出执行操作的最佳方法。我相信它胜过构建 if/else SQL 语句。
  • @espresso_coffee - 您提到了死锁,但没有说您是否绝对确定地确定了哪些语句是死锁的 - 以及所涉及的表上存在哪些索引。与其“猜测”什么可能解决问题,您应该首先跟踪死锁以确定导致它们的原因......只有这样您才能确定如何解决它们。

标签: sql-server sql-server-2008 stored-procedures coldfusion sql-merge


【解决方案1】:

您不应该猜测您的用户想要做什么。您的系统应该为每个用户操作使用不同的按钮/表单/功能来限制/限制/验证用户操作。

而且边读边写也引入了 Reader-Writer 死锁https://www.red-gate.com/simple-talk/sql/performance/sql-server-deadlocks-by-example/

这就是为什么我建议您需要将 qrySaveDictionary 拆分为 2 的原因:

<cftransaction action="begin">
    <cftry>
        <cfquery name="qryInsertDictionary" datasource="#dsn#">
            DECLARE @Status BIT = <cfqueryparam cfsqltype="cf_sql_bit" value="#trim(arguments.frm_status)#">;
            DECLARE @Name VARCHAR(50) = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#trim(arguments.frm_name)#">;
            DECLARE @Code CHAR(2) = UPPER(<cfqueryparam cfsqltype="cf_sql_char" maxlength="2" value="#trim(arguments.frm_code)#">);
            DECLARE @ActionDate DATETIME = CURRENT_TIMESTAMP;
            DECLARE @ActionID UNIQUEIDENTIFIER = <cfqueryparam cfsqltype="cf_sql_idstamp" value="#SESSION.AccountID#">;

                BEGIN
                     INSERT INTO Dictionary (
                        Status,Name,Code,ActionDt,ActionID
                    ) VALUES (
                        @Status,@Name,@Code,@ActionDate,@ActionID
                    )
                    SELECT SCOPE_IDENTITY() AS RecID
                END
        </cfquery>

        <cfset local.fnResults = {status : "200", message : "Record successully saved!", RecID : qrySaveDictionary.RecID}>

        <cfcatch type="any">
            <cftransaction action="rollback" />
                <cfset local.fnResults = {status : "400", class : "alert-danger", message : "Error! Please contact your administrator."}>
            </cfcatch>
        </cftry>
    </cftransaction>

<cftransaction action="begin">
    <cftry>
        <cfquery name="qryUpdateDictionary" datasource="#dsn#">
            DECLARE @Status BIT = <cfqueryparam cfsqltype="cf_sql_bit" value="#trim(arguments.frm_status)#">;
            DECLARE @Name VARCHAR(50) = <cfqueryparam cfsqltype="cf_sql_varchar" maxlength="50" value="#trim(arguments.frm_name)#">;
            DECLARE @Code CHAR(2) = UPPER(<cfqueryparam cfsqltype="cf_sql_char" maxlength="2" value="#trim(arguments.frm_code)#">);
            DECLARE @ActionDate DATETIME = CURRENT_TIMESTAMP;
            DECLARE @ActionID UNIQUEIDENTIFIER = <cfqueryparam cfsqltype="cf_sql_idstamp" value="#SESSION.AccountID#">;

                BEGIN
                    UPDATE Dictionary
                    SET
                        Status = @Status,
                        Name = @Name,
                        Code = @Code,
                        ActionDt = @ActionDate,
                        ActionID = @ActionID
                    WHERE RecID = @RecordID
                    SELECT @RecordID AS RecID
                END

        </cfquery>

        <cfset local.fnResults = {status : "200", message : "Record successully saved!", RecID : qrySaveDictionary.RecID}>

        <cfcatch type="any">
            <cftransaction action="rollback" />
                <cfset local.fnResults = {status : "400", class : "alert-danger", message : "Error! Please contact your administrator."}>
            </cfcatch>
        </cftry>
    </cftransaction>

让您的前端在用户选择他们的操作时决定何时插入或更新。

【讨论】:

  • 如果我把它分成两个函数,你是说应该防止死锁或数据库块?我正在阅读这篇文章,因此试图同时理解这个概念。我也应该将这两个语句拆分为同一个函数中的两个单独的查询,还是为每个进程设置单独的函数?
  • 我尝试删除 if exists 语句以防止死锁。您需要为单独的过程(插入和更新)提供单独的功能。您需要尝试一下,看看这是否能解决死锁。
  • 从您的回答中我看到您不使用IF EXIST。它们是在这种情况下没有帮助还是导致死锁?
  • 您使用“IF EXIST”选择相同的数据,然后尝试更新它。这是读写器死锁
  • 将其放入存储过程中会是一个不错的选择吗?另外,如果我这样做,我是否还应该拥有两个函数,例如 cffunction insertcffunction update 并在这两个函数中存储过程?
猜你喜欢
  • 2019-09-08
  • 1970-01-01
  • 1970-01-01
  • 2018-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多