【问题标题】:SQL Assembly WebResponse and String Parsing VERY slowSQL 程序集 WebResponse 和字符串解析非常慢
【发布时间】:2018-10-29 09:57:46
【问题描述】:

所以我正在快速学习 C# 的方法(继承了这个问题的完整菜鸟);我编写了以下代码,该代码调用了一个返回 JSON 格式的 Web 服务,该 JSON 格式并不总是正确的。这里的任务是获取 JSON 字符串并将其分解为数组段,然后插入到 SQL 表中以进行进一步的解析和测试。 IE。如果返回字符串类似于

   {1234:{5678:{1:{"key":"val","key":"val"},{2:{"key":"val","key":"val"}}}}

那么行将是:

{1234}
{5678}
{1:{"key":"val","key":"val"}
{2:{"key":"val","key":"val"}

这是 .NET 3.0 和 SQL Server 2008 R2(旧版本)。 这是我的工作代码:

 public partial class UserDefinedFunctions
    {
         [Microsoft.SqlServer.Server.SqlFunction(DataAccess = 
    DataAccessKind.Read)]
    public static SqlString TestParse(SqlString uri, SqlString username, SqlString passwd, SqlString postdata)
    {
            //-----
           // The SqlPipe is how we send data back to the caller
       SqlPipe pipe = SqlContext.Pipe;
        SqlString document;
        try
        {
            // Set up the request, including authentication
            WebRequest req = WebRequest.Create(Convert.ToString(uri));
            if (Convert.ToString(username) != null & Convert.ToString(username) != "")
            {
                req.Credentials = new NetworkCredential(
                    Convert.ToString(username),
                    Convert.ToString(passwd));
            }
            ((HttpWebRequest)req).UserAgent = "CLR web client on SQL Server";

            // Fire off the request and retrieve the response.
            using (WebResponse resp = req.GetResponse())
            {

                using (Stream dataStream = resp.GetResponseStream())
                {
                    //SqlContext.Pipe.Send("...get the data");
                    using (StreamReader rdr = new StreamReader(dataStream))
                    {
                        document = (SqlString)rdr.ReadToEnd();
                        rdr.Close();

                        //-----
                        string connectionString = null;
                        string sql = null;
                        connectionString = "Data source= 192.168.0.5; Database=Administration;User Id=Foo;Password=Blah; Trusted_Connection=True;";
                        using (SqlConnection cnn = new SqlConnection(connectionString))
                        {
                            sql = "INSERT INTO JSON_DATA (JSONROW) VALUES(@data)";
                            cnn.Open();
                            using (SqlCommand cmd = new SqlCommand(sql, cnn))
                            {

                                String payload = "";
                                String nestpayload = "";
                                int nests = 0;
                                String json = document.ToString();
                                /*first lets do some housekeeping on our payload; double closing curly braces need to be escaped (with curly braces!) in order to keep them in the string.*/
                                json = json.Replace("\\", "");
                                int i = json.Length;
                                //return new SqlString(json);
                                while (i > 1)
                                {
                                    /*find the first closing "}" in the string and then check to see if there are more than one.
                                    We need to read the data up to each closing brace, pull off that substring and process it for each iteration until the string is gone.*/
                                    int closingbrace = json.IndexOf("}"); //First closing brace
                                    int nextbrace = Math.Max(0, json.IndexOf("{", closingbrace)); //Next opening brace
                                    String ChkVal = json.Substring(closingbrace + 1, Math.Max(1, nextbrace - closingbrace)); //+1 to ignore the 1st closing brace
                                    int checks = Math.Max(0, ChkVal.Length) - Math.Max(0, ChkVal.Replace("}", "").Length);
                                    payload = json.Substring(0, Math.Max(0, (json.IndexOf("}") + 1)));
                                    /*Remove the payload from the string*/
                                    json = json.Substring(payload.Length + 1);

                                    /*"nests" is how many nested levels excluding the opening brace for the closing brace we found.*/
                                    nests = (payload.Length - payload.Replace("{", "").Length);
                                    /*If we have more then one nest level check to see if any of them go with the payload*/

                                    if (nests > 1)
                                    {
                                        /*Break out the nested section and remove it from the payload.*/
                                        nestpayload = payload.Substring(0, payload.LastIndexOf("{"));
                                        payload = payload.Substring(payload.LastIndexOf("{"), payload.Length - payload.LastIndexOf("{"));

                                        while (nests > 1)
                                        {
                                            if (checks > 0) //# of right braces in payload equals number of left-side nests go with the payload
                                            {
                                                // payload = nestpayload.Substring(Math.Max(0, nestpayload.LastIndexOf("{")), Math.Max(0, nestpayload.Length) - Math.Max(0, (nestpayload.LastIndexOf("{")))) + payload;//The second Math.Max defaults to 1; if we got here there is at minimum one "{" character in the substring
                                                payload = nestpayload.Substring(nestpayload.LastIndexOf("{")) + payload;
                                                nestpayload = nestpayload.Substring(0, Math.Max(0, Math.Max(0, nestpayload.LastIndexOf("{"))));
                                                checks--;
                                                nests--;
                                            }
                                            else
                                            {
                                                /*If we got here there are no more pieces of the nested data to append to the payload.
                                                 We use an array and string.split to keep the nest ordering correct.*/
                                                string[] OrderedNest = nestpayload.Split('{');
                                                for (int s = 0; s < OrderedNest.Length; s++)
                                                {
                                                    if (OrderedNest[s] != "")
                                                    {
                                                        cmd.Parameters.AddWithValue("@data", "{" + OrderedNest[s].Replace(":", "}"));
                                                        cmd.ExecuteNonQuery();
                                                        cmd.Parameters.Clear();
                                                    }
                                                }

                                                //cmd.Parameters.AddWithValue("@data", nestpayload.Substring(Math.Max(0,nestpayload.LastIndexOf("{"))).Replace(":","}"));
                                                //cmd.Parameters.AddWithValue("@data", OrderedNest[1].Replace(":","}")+OrderedNest[2]);
                                                // cmd.ExecuteNonQuery();
                                                //cmd.Parameters.Clear();
                                                //nests = Math.Max(0, nests - 1);
                                                nests = 0;
                                                //nestpayload = nestpayload.Substring(0, Math.Max(0, Math.Max(0,nestpayload.LastIndexOf("{"))));

                                            }
                                        }
                                    }


                                    /*At the very end payload will be a single "}"; check for this and discard the last row*/
                                    if (payload != "}")
                                    {
                                        cmd.Parameters.AddWithValue("@data", new SqlChars(payload));
                                        cmd.ExecuteNonQuery();
                                        cmd.Parameters.Clear();
                                    }

                                    /*Get the new string length*/
                                    i = json.Length;
                                    payload = "";

                                }

                            }
                        }
                        //-----

                        /*  }
                          catch (Exception e)
                          {
                              return e.ToString();
                          }*/
                    }

           // Close up everything...
                    dataStream.Close();
                }
                resp.Close();
                // .. and return the output to the caller.

            }//end using
            return ("Finished");
        }
        catch (WebException e)
        {

            throw e;
        }                   
  }
}

虽然它有效,但速度非常慢; 4 分钟以上将 1500 行写入服务器。每天一次,这将需要写入约 60,000 条记录;其余时间可能会发布并返回 100 条记录(我还没有完成 POST 部分)。我敢肯定,我在这里做的很多不恰当的事情都会导致问题,但我完全不知道从哪里开始。我很兴奋,我能从中得到正确的回应!任何想法/想法/帮助/同情将不胜感激。

【问题讨论】:

  • 首先找出代码的哪一部分花费的时间最多。添加一些测试代码,输出代码各部分的开始和结束时间。我会惊讶地发现对数据库的插入是原因。如果是数据库,那么可能需要对数据库进行碎片整理。您使用的是 SQL Express 还是 SQL Standard?数据库有多大?
  • @jdweng SQL Enterprise。 8个核心。这个特定的数据库是管理性的,非常小。即便如此,它也会定期进行碎片整理。你能帮我一些关于如何输出时间的想法吗?我是将它们输出回 SQL Server 消息窗口还是必须使用调试器和控制台窗口来完成?我安装了 VS2012,当我尝试调试时,我一直收到 MSVMON.EXE 错误。还是没弄明白!
  • 我会先删除项目的 bin 文件夹(先复制一份)。这将强制您的所有代码重新编译。 VS 编译器并不总是能够识别库更新,并且您的代码可能与您当前版本的库不完全兼容。我也不喜欢“使用”语句,因为它总是报告异常。我宁愿使用 try/catch 并确保显示所有异常消息。 using 将继续通过没有错误消息的异常,然后您最终在下一个代码块中失败并在错误的代码块中查找错误。

标签: c# sql-server web-services httpwebrequest sqlclr


【解决方案1】:

这里有几个问题,其中最重要的是您似乎已将您的“sa”密码发布到这些公共互联网上。以下是我看到的代码问题:

  1. 虽然可以在 SQLCLR 中进行 Web 服务调用,但这绝对是一个高级主题,充满了陷阱。这不是 SQLCLR 的新手/初学者应该承担的事情,它本身已经是常规 .NET 编程的一个细微的子集。
  2. 去掉SqlPipe 行和它上面的注释行。函数不会通过SqlPipe 将数据传回给调用者;这适用于存储过程。
  3. 你可能不应该使用WebRequest
  4. document 应该是 string,而不是 SqlString。您永远不会返回 document,只会将其转换回 string,所以应该就是这样。
  5. 使用HttpWebRequest 而不是WebRequest。这样您就不必偶尔将其转换为 HttpWebRequest
  6. 不要将SqlString 输入参数转换为string(例如Convert.ToString(uri))。所有Sql* 类型都有一个Value 属性,该属性返回本机.NET 类型中的值。因此,只需使用 uri.Value 等等。
  7. 不要通过Convert.ToString(username) != null 检查NULL 输入。所有Sql* 类型都有一个可以检查的IsNull 属性。所以改为使用!username.IsNull
  8. 不要在保持远程HttpWebRequest 连接打开的同时进行所有文本处理(尤其是与另一个系统联系以进行逐行插入的处理)。 唯一 您应该在using (WebResponse resp = req.GetResponse()) 中做的事情是填充document 变量。不要对document 的内容进行任何处理,直到您在最外层的using() 之外。
  9. 不要进行单独的插入(即while (i &gt; 1) 循环)。他们甚至没有参与交易。如果您在文档中间遇到错误,则说明您已经加载了部分数据(除非此过程没问题)。
  10. 总是模式限定数据库对象。意思是,JSON_DATA 应该是 dbo.JSON_DATA(或者如果不是 dbo,则使用任何 Schema)。
  11. 在您的 connectionString 中,您同时拥有 Id/Password 和 Trusted_Connection。不要同时使用它们,因为它们是互斥的选项(如果同时使用,则忽略 ID/密码,仅使用 Trusted_Connection)。
  12. 请不要以sa 登录或让您的应用程序以sa 登录。那只是在乞求一场灾难。
  13. 您是否连接到与运行此 SQLCLR 对象不同的 SQL Server 实例?如果是同一个实例,最好将其更改为SqlProcedure,以便可以使用Context_Connection=True; 作为连接字符串。那是附加到从中调用它的会话的进程内连接。
  14. 不要使用Parameters.AddWithValue()。馊主意。使用特定且适当的数据类型创建 SqlParameter。然后通过Add() 添加到Parameters 集合。

可能还有其他问题,但这些是显而易见的问题。正如我在第 1 点中所说的,您可能会对此感到困惑。不要试图消极,只是试图避免 SQLCLR 的另一个糟糕的实现,这通常会导致对这个原本非常有用的特性的负面看法。如果你想追求这个,那么请先对 SQLCLR 的工作原理、最佳实践等进行更多研究。一个好的起点是我在 SQL Server Central 上写的关于这个主题的系列文章:Stairway to SQLCLR

或者,另一种选择是使用SQL# SQLCLR 库(我编写的)完整版中提供的INET_GetWebPages SQLCLR TVF。此选项不是免费的,但它允许您简单地安装 Web 请求片段,然后您只需要在 SQLCLR 标量 UDF 中单独解析返回的文档(这可能是最好的方法,即使您执行 Web 请求您自己的函数/存储过程)。实际上,如果您要插入到同一 SQL Server 实例中的表中,您可以为文档解析器创建一个 SQLCLR TVF,并使用 yield return 将每个 OrderedNest 值传回(将结果流回)并用作如下:

DECLARE @JSON NVARCHAR(MAX);

SELECT @JSON = [content]
FROM   SQL#.INET_GetWebPages(@uri, .....);

INSERT INTO dbo.JSON_DATA (JSONROW)
  SELECT [column_name]
  FROM   dbo.MyBrokenJsonFixerUpper(@JSON);

祝你好运!

【讨论】:

  • 哦,“复制粘贴”的陷阱!...幸好只是一个测试框,但无论如何都已修复。是的,先进的,是的,我背负着它......下沉或游泳!优秀的 cmets,我将解决每个问题。不,我们不会使用“sa”;在最初测试时,为了避免权限问题,很早就将其扔在那里。很好地抓住了 WebRequest 保持打开状态;我想我需要仔细看看所有这些是如何嵌套在一起的。关于#9,我对更好的方法感到好奇;这应该是批量插入吗?在阅读了(一个)大型数据集的内存消耗等信息后,我远离了这一点。
  • 另外...您能否详细说明为什么 Parameters.AddWithValue 不好?我这样做的方式不是已经设置了适当的数据类型吗?等等,找到这个[链接]blogs.msmvps.com/jcoehoorn/blog/2014/05/12/…
  • @A.Guattery 是的,该博客文章是大部分(或全部)问题。另外,仅供参考,更正密码很好,但请记住,问题的编辑历史可通过问题底部您姓名左侧的“编辑”链接公开获得。至于批量插入,是的,您可以填写 DataTable 的值并通过 SqlBulkCopy 一次将它们全部传回,或者您可以按照我的建议让 TVF 吐回结果并执行 @987654370 @ 用它。然后您可以将DataAccessKind 设置为None,这对性能有一点帮助。
  • 关于“编辑”链接的有趣点!我根据我们的 cmets 重写了代码,并将问题缩小到 while (i &lt; 1) 循环开头的 IndexOfSubstring 调用。一遍又一遍地重新创建的超大 json 字符串阻碍了工作。我在想,如果我创建一个 Datatable 并先将该字符串拆分成它会更好。然后遍历该表并进行相应的解析。
  • 你好,@A.Guattery。不确定您是否需要 DataTable 来保存字符串列表。你不能使用string[] 甚至List&lt;string&gt; 吗?您从 Web 服务获取的这些文档有多少个字符?
【解决方案2】:

我将这个问题标记为已回答,因为很明显需要重写和重新思考我的原始脚本。 @Solomon Rutzky 赞成提供有用的信息,这些信息让我得出了这个结论。对于那些 这里感兴趣的是重写:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Globalization;
// Other things we need for WebRequest
using System.Net;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void ApiParser(SqlString uri, SqlString user, SqlString pwd, SqlString postd)
    {
        // Create an SqlPipe to send data back to the caller
        SqlPipe pipe = SqlContext.Pipe;
        //Make sure we have a url to process
        if (uri.IsNull || uri.Value.Trim() == string.Empty)
        {
            pipe.Send("uri cannot be empty");
            return;
        }
    try
    {
        //Create our datatable and get the table structure from the database
        DataTable table = new DataTable();
        string connectionString = null;
        //connectionString = "Data source= 192.168.0.5; Database=Administration; Trusted_Connection=True;";
        connectionString = "Data Source=(localdb)\\ProjectsV12;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        using (SqlConnection gts = new SqlConnection(connectionString))
        {
            gts.Open();
            using (SqlDataAdapter adapter = new SqlDataAdapter("SELECT TOP 0 * FROM sp_WebSvcs.dbo.JSON_DATA", gts))
            {
                adapter.Fill(table);
            }
        }

        // Send a message string back to the client.
        pipe.Send("Beginning Api Call...");
        String json = "";
        // Set up the request, including authentication
        WebRequest req = HttpWebRequest.Create(uri.Value);
        if (!user.IsNull & user.Value != "")
        {
            req.Credentials = new NetworkCredential(user.Value, pwd.Value);
        }
        ((HttpWebRequest)req).UserAgent = "CLR web client on SQL Server";

        // Fire off the request and retrieve the response.
        using (WebResponse resp = req.GetResponse())
        {

            using (Stream dataStream = resp.GetResponseStream())
            {

                using (StreamReader rdr = new StreamReader(dataStream))
                {
                    json = (String)rdr.ReadToEnd();

                    rdr.Close();
                }

                // Close up everything...
                dataStream.Close();
            }
            resp.Close();

        }//end using resp
        pipe.Send("Api Call complete; Parsing returned data...");
        int i = 0;
        String h = "";
        String l = "";
        int s = 0;
        int p = 0;
        int b = 0;
        int payload = 0;
        foreach (string line in json.Split(new[] { "}," }, StringSplitOptions.None))
        {
            if (line != "")
            {
                l = line;
                i = l.Replace("{", "").Length + 1;
                p = l.LastIndexOf("{");
                if (line.Length > i) //we find this at the beginning of a group of arrays
                {

                    h = line.Substring(0, p - 1);
                    s = Math.Max(0, h.LastIndexOf("{"));
                    if (h.Length > s && s != 0)
                    /*We have a nested array that has more than one level.
                     *This should only occur at the beginning of new array group.
                     *Advance the payload counter and get the correct string from line.*/
                    {
                        payload++;
                        l = line.Substring(s, line.Length - s);
                    }


                    h = (s >= 0) ? h.Substring(0, s) : h;
                    //=============
                    /*At this point 'h' is a nest collection. Split and add to table.*/
                    string[] OrderedNest = h.Split('{');
                    for (int z = 0; z < OrderedNest.Length; z++)
                    {
                        if (OrderedNest[z] != "")
                        {
                            table.Rows.Add(payload, "{" + OrderedNest[z].Replace(":", "").Replace("[","").Replace("]","") + "}");
                        }
                    }
                    //=============

                }
                else
                {
                    h = null;
                }
                //at this point the first character in the row should be a "{"; If not we need to add one.
                if (l[0].ToString() != "{")
                {
                    l = "{" + l;
                }

                if (l.Replace("{", "").Length != l.Replace("}", "").Length) //opening and closing braces don't match; match the closing to the opening
                {
                    l = l.Replace("}", "");

                    b = l.Length - l.Replace("{", "").Length;

                    l = l + new String('}', b);
                }
                table.Rows.Add(payload, l.Replace("\\\"", "").Replace("\\", "").Replace("]","").Replace("[",""));

            }
        }
        //====

        using (SqlConnection cnn = new SqlConnection(connectionString))
        {
            cnn.Open();
            using (SqlBulkCopy copy = new SqlBulkCopy(cnn))
            {
                copy.DestinationTableName = "sp_WebSvcs.dbo.JSON_DATA";
                copy.WriteToServer(table);
            }
        }
        //====

    } //end try
    catch (Exception e)
    {
        pipe.Send("We have a problem!");
        throw new Exception("\n\n" + e.Message + "\n\n");
    }
    pipe.Send("Parsing complete");

}

}

【讨论】:

    猜你喜欢
    • 2016-08-12
    • 2015-07-28
    • 2021-08-04
    • 2021-03-02
    • 2010-10-13
    • 1970-01-01
    • 2011-12-18
    • 2013-09-03
    • 2014-09-03
    相关资源
    最近更新 更多