zoukankan      html  css  js  c++  java
  • 使用mssql2008新特性(存储过程参数类型使用"用户自定义表"来实现批量DML更新多表)解决项目里遇到的性能问题

    使用MSSQL2008有一段时间了,因为平常参与项目开发一般都使用NHbernate、Entity Framework等这些ORM工具来做数据的存储,所以并没有花时间去学习mssql2008,还是用mssql2000那会儿积累的经验来使MSSQL2008。当然也是粗略的了解到MSSQL2008也有表分区啊、自定义数据类型等。代码重构前一次操作需要数分钟,甚至半小时的操作重构后只需数秒至十几秒,前端经常卡死Ajax调用后台方法,后台不返回执行结果,后台出现超时错误等。

    找到代码重构需要解决的问题:

    1、原有代码实现方式是Entity Framework和拼接SQL混合的方式、因为需要同时更新多个表,Entity Framework实现起来是异常的吃力啊,前同事喜欢拼接大段的TSQL脚本在代码里实现,我的直觉是自己熟悉数据库,第一个想的就是使用存储过程来优化。见下图写法:

    看到的问题就是很混乱,没有事务控制,现在系统就是经常出现异常时有些数据提交了有些没提交,部分进了第一个表,第二个表里状态没更新,你懂的,如此如此那般那般,我得经常维护这个项目,一直都有事情做。呵呵...

    2、解决前台页面传递过来的JSON里处理多条记录,需要使用循环并嵌套循环的问题,提高效率。

    解决的办法:

    昨天看到知识库里的博文:“SQL Server 高性能写入的一些总结”,最后部分的使用MSSQL2008表参数来达到批量高效插入记录,转念一箱如果使用表参数一次传入需要循环多次传递的参数不是提高了速度吗?测试下来果真OK,效率提升了不止一点点啊,数十倍的提升!

    C#调存储过程:

     #region 准备DataTable
                DataTable dt = new DataTable();
                dt.Columns.Add(new DataColumn("RequestKeyID", typeof(Guid)));
                dt.Columns.Add(new DataColumn("EntityCode", typeof(string)));
                dt.Columns.Add(new DataColumn("Thirdparty", typeof(string)));
                dt.Columns.Add(new DataColumn("PayStatus", typeof(int)));
                dt.Columns.Add(new DataColumn("LoginUserName", typeof(string)));
                foreach (JavaScriptObject r in rows)
                {
                    year = int.Parse(r["Year"].ToString());
                    month = int.Parse(r["Month"].ToString());
                    var row = dt.NewRow();
                    row["RequestKeyID"] = Guid.NewGuid();
                    row["EntityCode"] = r["Entity"].ToString();
                    row["Thirdparty"] = r["ThirdPart"].ToString();
                    row["PayStatus"] = status;
                    row["LoginUserName"] = login;
                    dt.Rows.Add(row);
                }
                #endregion           
                using (WHSEntities db = new WHSEntities())
                {
                    db.AdoExecuteProc(WebConfigurationManager.ConnectionStrings["WHSEntities"].ConnectionString,
                                      "Proc_StreamBatchPay",
                                      new System.Data.SqlClient.SqlParameter[]
                                      {
                                          new System.Data.SqlClient.SqlParameter("@P_BatchRequestDataTable",SqlDbType.Structured){Value=dt},
                                          new System.Data.SqlClient.SqlParameter("@PayYear",SqlDbType.Int){Value=year},
                                          new System.Data.SqlClient.SqlParameter("@PayMonth",SqlDbType.Int){Value=month},
                                          new System.Data.SqlClient.SqlParameter("@PayStatus",SqlDbType.Int){Value=status}
                                      });
                }

    存储过程:

    ALTER proc [dbo].[Proc_StreamBatchPay] 
                @P_BatchRequestDataTable DT_StreamBatchRequest READONLY , --临时表多个支付请求
                @PayYear int ,  --支付的财务年
                @PayMonth int,   --支付的财务月
                @PayStatus int   --是支付1 还是拒绝0           
    /*
      ***XXXXX过程
      Date:  2013-3-14
        原因:
      1、批量插入和更新,加快页面反映速度,原来代码是EF和SQL混搭没有事务处理和页面反馈慢
      2、使用事务来保证数据一致性
      3、操作日志痕迹的保留
    */         
    AS
    DECLARE @EntityCode  nvarchar(20) 
    DECLARE @Thirdparty nvarchar(40)
    DECLARE @LoginUserName nvarchar(60) 
    DECLARE @CountOfPaymentID int
    DECLARE @SerialID uniqueidentifier
    DECLARE @PaymentID uniqueidentifier
    BEGIN
     SET XACT_ABORT ON;
     SET NOCOUNT ON;
     BEGIN TRY
        --开启事务
        BEGIN TRANSACTION;
            INSERT INTO dbo.FMS_WH_PaymentDetail                                                            
            SELECT NEWID(),@PayStatus,p.PaymentID,GETDATE(),p.Accrual,p.UpdateBy,GETDATE(),p.ThirdPart,p.Category 
            FROM dbo.FMS_WH_Payment p 
                 LEFT JOIN @P_BatchRequestDataTable i ON p.Entity = i.EntityCode and p.ThirdPart = i.Thirdparty
            WHERE p.Year = @PayYear and  p.Month = @PayMonth and  p.Category = '3PLStream';        
            --支付     
            IF @PayStatus = 1
                BEGIN
                   UPDATE dbo.FMS_WH_Payment SET UpdateBy= p.LoginUserName,UpdateDate=GETDATE(),Actual=Accrual
                   FROM @P_BatchRequestDataTable p
                   WHERE Entity = p.EntityCode  AND ThirdPart = p.Thirdparty                                     
                         AND Category = '3PLStream'  AND Year = @PayYear AND Month = @PayMonth; 
                        
                   SELECT p1.SerialID,p1.PaymentID,p1.OldYear,p1.OldMonth,p1.[Year],p1.[Month],p1.Entity,p1.ThirdPart
                   into #PayMent1Temp
                   FROM dbo.FMS_WH_Payment1 p1 
                   WHERE p1.Year = @PayYear AND
                         p1.Month = @PayMonth AND
                         p1.Entity+'-'+p1.ThirdPart IN (SELECT E.EntityCode+'-'+E.Thirdparty FROM @P_BatchRequestDataTable E) AND                     
                         p1.Category = '3PLStream';
                         
                   --游标处理PayMent1里对应的记录
                   DECLARE CUR_PAYMENT1 CURSOR FOR SELECT SerialID,PaymentID FROM #PayMent1Temp 
                   OPEN CUR_PAYMENT1;
                   FETCH NEXT FROM CUR_PAYMENT1  INTO  @SerialID,@PaymentID;
                   WHILE @@FETCH_STATUS = 0
                      BEGIN
                         SELECT @CountOfPaymentID = COUNT(*) FROM FMS_WH_Payment1 
                         WHERE  PaymentID=@PaymentID;
                         --没跨月,当月发生当月支付
                         IF @CountOfPaymentID = 1 
                           BEGIN
                             UPDATE FMS_WH_Payment1 SET Actual=Accrual 
                             WHERE SerialID = @SerialID;
                           END
                         --跨月,当月发生下月支付
                         IF @CountOfPaymentID = 2 
                            BEGIN
                                --更新发生月那笔payment1记录实际付款金额
                                UPDATE FMS_WH_Payment1 SET Actual=Accrual
                                WHERE SerialID = @SerialID;
                                --删除结转的那笔用于显示的payment1记录
                                DELETE FMS_WH_Payment1 WHERE PaymentID=@PaymentID AND OldMonth <> [Month];
                            END    
                         --跨月,当月发生下下月支付 原则上不允许流量跨2月
                         IF @CountOfPaymentID = 3 
                            BEGIN
                                --更新发生月那笔payment1记录实际付款金额
                                UPDATE FMS_WH_Payment1 SET Actual=Accrual
                                WHERE PaymentID=@PaymentID AND OldMonth = [Month];
                                --删除结转的那2笔用于显示的payment1记录
                                DELETE FMS_WH_Payment1 WHERE PaymentID=@PaymentID AND OldMonth <> [Month];
                            END         
                         FETCH NEXT FROM CUR_PAYMENT1  INTO  @SerialID,@PaymentID         
                      END
                   CLOSE CUR_PAYMENT1;
                   DEALLOCATE CUR_PAYMENT1;
                   --操作成功,写日志
                   INSERT INTO fms_system_log(dtDate,sThread,sLevel,sLogger,sMessage,sException)
                   SELECT GETDATE(),'DBLOG','INFO','dbProc','02|执行了'+CAST(@PayYear AS VARCHAR)+'\'+CAST(@PayMonth AS VARCHAR)+'批量流量费用支付操作。','';  
               END
              --拒绝
              IF @PayStatus = 0
                BEGIN
                   UPDATE dbo.FMS_WH_Payment SET UpdateBy= p.LoginUserName,UpdateDate=GETDATE(),[status]=0
                   FROM @P_BatchRequestDataTable p
                   WHERE Entity = p.EntityCode AND ThirdPart = p.Thirdparty                                     
                           AND Category = '3PLStream'  AND Year = @PayYear  AND Month = @PayMonth; 
                   --写日志
                   INSERT INTO fms_system_log(dtDate,sThread,sLevel,sLogger,sMessage,sException)
                   SELECT GETDATE(),'DBLOG','INFO','dbProc','02|执行了'+CAST(@PayYear AS VARCHAR)+'\'+CAST(@PayMonth AS VARCHAR)+'批量流量费用拒绝支付的操作。','';
                END
        --提交事务
        COMMIT TRANSACTION;
     END TRY
     BEGIN CATCH    
        IF (XACT_STATE()) = -1
        BEGIN
            ROLLBACK TRANSACTION;
            --异常日志记录
            INSERT INTO fms_system_log(dtDate,sThread,sLevel,sLogger,
                                       sMessage,sException)
            SELECT GETDATE(),'DBLOG','ERROR','dbProc',
                   '异常代码:'+CAST(ERROR_NUMBER() AS VARCHAR)+'\异常消息:'+ERROR_MESSAGE(),CAST(ERROR_NUMBER() AS VARCHAR)
        END;   
        IF (XACT_STATE()) = 1
        BEGIN
            COMMIT TRANSACTION;   
        END;
     END CATCH;
    END

    这是特定业务写的存储过程没什么用处,也不需要看懂,就是看看表参数是怎么用的,怎么使用事务来优化控制数据的一致性。看存储过程里的注释即可。

    别告诉哥EF怎么调整性能好,哥没那时间去学习,多年数据库编程经验哥自信在这个时刻ADO.NET对于我是最可靠的。

    OK。搞定收工,主要是留下思路给自己以后回顾的,万一能帮到你也解决了类似问题,那我就更开心了!请不要说没有截图对比效率提升,无图无真相;是真不方便哦。没效果我就不会分享了。

  • 相关阅读:
    第四章 使用jQuery操作DOM
    第三章 jQuery中的事件与动画
    第二章 jQuery选择器
    第一章 jQuery基础
    第五章 JavaScript对象及初识面向对象
    第四章 JavaScript操作DOM对象
    第三章 JavaScript操作BOM对象
    第二章 JavaScript核心语法
    第一章 Javascript基础
    第九章 MySQL中LIMIT和NOT IN案例
  • 原文地址:https://www.cnblogs.com/datacool/p/mssql2008_user_table_para.html
Copyright © 2011-2022 走看看