【发布时间】:2018-11-30 20:35:28
【问题描述】:
我开发了一个 WPF 客户端,它使用 Azure SQL 数据库来查询发货发票。
客户一直抱怨货件清单的加载时间过长,我发现对于每个货件,应用程序都会查询 Azure SQL 数据库以获取货件发票,这需要时间。
查询是在每个货物的 foreach 循环中进行的,并且没有其他方法可以做到这一点,因为货物是通过在客户端中过滤从 Web 服务加载的。 发票表没有要使用过滤器的列。
为了测试这一点,我做了以下操作:
创建了一个 TSQL 脚本,该脚本在 100 个装运上运行动态 sql 以获取发票并在 SQL Server 管理工作室中执行它。 对 100 个装运中的每一个都执行动态 SQL。 我记录了动态 sql 执行之前和之后的时间 每次出货的动态sql执行时间为0毫秒,足够快了。
在 C# 中创建了一个控制台应用程序,用于从 Azure 收集 100 件货物 将它们存储在列表中并运行查询以在列表上的 foreachloop 中获取发票。每批货物的执行时间在 43 到 58 毫秒之间变化,这太长了。
-
为了进行基准测试,我在网络中我们自己的一台 SQL 服务器上创建了 Invoice 表,从 Azure SQLdatabase 复制数据并运行控制台应用程序。 每批货物的执行时间在 0 到 15 毫秒之间变化,这已经足够快了。
所以我的问题是,在 Microsoft SQLServer 管理工作室与控制台应用程序中循环运行 100 个查询之间怎么会有如此巨大的差异? MSSMS 有神奇的联系吗?
示例代码:
//Console application
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Data;
using System.IO;
namespace ConsoleApp8
{
class Program
{
public struct res
{
public string ShipmentId;
public DateTime St;
public DateTime Sl;
public double Exectimems;
public res(string shipmentid, DateTime st)
{
ShipmentId = shipmentid;
St = st;
Sl = new DateTime();
Exectimems = 0;
}
}
static void Main(string[] args)
{
//string connstr = "Data Source=LocalNetWorkSQLServer;Initial Catalog=Dummy;User ID=sa;Password=somepassword;MultipleActiveResultSets=True";
string connstr = "Server = tcp:Server.database.windows.net;Database=Dummy;User ID =Username@Server;Password=somepassword;Trusted_Connection=False;Encrypt=True;MultipleActiveResultSets=True;";
List<string> Shipments = new List<string>();
List<res> results = new List<res>();
SqlConnectionStringBuilder scb = new SqlConnectionStringBuilder(connstr);
//Get all shipments to find invoices on and put them in a list.
string sql = "Select top 100 ShipmentId"
+ " From("
+ " Select distinct shipmentid"
+ " From TKL_Invoices With(readuncommitted)"
+ " Where OfficeId = 'swe') x";
using (SqlConnection cnn = new SqlConnection(connstr))
{
cnn.Open();
using (SqlCommand cmd = new SqlCommand(sql, cnn))
{
cmd.CommandType = System.Data.CommandType.Text;
SqlDataReader rd = cmd.ExecuteReader();
while (rd.Read())
{
Shipments.Add(rd["ShipmentId"].ToString());
}
}
cnn.Close();
}
//Get first invoice on each shipment
sql = "TKL_GetSavedInvoices";
using (SqlConnection cnn = new SqlConnection(connstr))
{
cnn.Open();
foreach (string Shipment in Shipments)
{
res r = new res(Shipment, DateTime.Now);
using (SqlCommand cmd = new SqlCommand(sql, cnn))
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@ShipmentId", Shipment);
cmd.Parameters.AddWithValue("@OfficeId", "swe");
cmd.Parameters.AddWithValue("@Dbg", "1");
string invoice = cmd.ExecuteScalar().ToString();
r.Sl = DateTime.Now;
TimeSpan timeDiff = r.St - r.Sl;
r.Exectimems = timeDiff.TotalMilliseconds;
results.Add(r);
Console.WriteLine(Shipment, r.Exectimems.ToString());
}
}
cnn.Close();
}
//Log result to file
string prefix = "NetWorkSQLServer";
if (scb.DataSource.ToLower().Contains("tcp:"))
prefix = "Azure";
string filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WPF_Client_" + prefix + "_QueryResult_" + scb.DataSource.Replace(':','_') + "." + scb.InitialCatalog + ".csv");
if (File.Exists(filename))
File.Delete(filename);
StringBuilder sb = new StringBuilder();
sb.Append("ShipmentId;TimeBeforeQyery;TimeAfterQyery;QueryTimeMilliseconds");
foreach (res r in results)
{
sb.Append(Environment.NewLine)
.Append(r.ShipmentId).Append(";")
.Append(r.St.ToLongTimeString()).Append(";")
.Append(r.Sl.ToLongTimeString()).Append(";")
.Append(r.Exectimems.ToString()).Append(";");
}
File.WriteAllText(filename, sb.ToString());
Console.WriteLine("Done");
Console.ReadKey();
}
}
}
SQL数据库表:
/****** Object: Index [ix_TKL_Invoices001] Script Date: 2018-06-21 15:12:43 ******/
DROP INDEX [ix_TKL_Invoices001] ON [dbo].[Tkl_Invoices]
GO
/****** Object: Table [dbo].[Tkl_Invoices] Script Date: 2018-06-21 15:12:43 ******/
DROP TABLE [dbo].[Tkl_Invoices]
GO
/****** Object: Table [dbo].[Tkl_Invoices] Script Date: 2018-06-21 15:12:43 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Tkl_Invoices](
[ShipmentId] [varchar](50) NOT NULL,
[OfficeId] [varchar](50) NULL,
[InvoiceNo] [varchar](50) NOT NULL,
CONSTRAINT [PK_Tkl_Invoices] PRIMARY KEY CLUSTERED
(
[ShipmentId] ASC,
[InvoiceNo] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING ON
GO
/****** Object: Index [ix_TKL_Invoices001] Script Date: 2018-06-21 15:12:43 ******/
CREATE NONCLUSTERED INDEX [ix_TKL_Invoices001] ON [dbo].[Tkl_Invoices]
(
[OfficeId] ASC,
[ShipmentId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
--Data for the table
Insert into Tkl_Invoices(ShipmentId, OfficeId, InvoiceNo)
Select '1511546743', 'SWE', '250279'
Union Select '1512550199', 'SWE', '1111111'
Union Select '1512554318', 'SWE', '250249'
Union Select '1601561108', 'SWE', '250279'
Union Select '1602561957', 'SWE', '249700'
Union Select '1602562876', 'SWE', '250582'
Union Select '1602566412', 'SWE', '250577'
Union Select '1602567219', 'SWE', '249699'
Union Select '1602567387', 'SWE', '250577'
Union Select '1603568831', 'SWE', '250577'
Union Select '1603569044', 'SWE', '250326'
Union Select '1603569143', 'SWE', '249846'
Union Select '1603569720', 'SWE', '249994'
Union Select '1603570257', 'SWE', '250279'
Union Select '1603570259', 'SWE', '250582'
Union Select '1603570940', 'SWE', '1234'
Union Select '1603572374', 'SWE', '250118'
Union Select '1603572376', 'SWE', '250200'
Union Select '1603572378', 'SWE', '250034'
Union Select '1603572450', 'SWE', '249923'
Union Select '1603572450', 'SWE', '250262'
Union Select '1603572601', 'SWE', '249927'
Union Select '1603572603', 'SWE', '250577'
Union Select '1603573496', 'SWE', '249701'
Union Select '1603573826', 'SWE', '249712'
Union Select '1603574012', 'SWE', '250577'
Union Select '1603574022', 'SWE', '250055'
Union Select '1603574184', 'SWE', '249888'
Union Select '1603574193', 'SWE', '249702'
Union Select '1603574195', 'SWE', '250239'
Union Select '1603574417', 'SWE', '249921'
Union Select '1603574610', 'SWE', '1000000'
Union Select '1603574652', 'SWE', '249704'
Union Select '1603574865', 'SWE', '249706'
Union Select '1603574874', 'SWE', '250109'
Union Select '1603575030', 'SWE', '250175'
Union Select '1603575032', 'SWE', '250172'
Union Select '1603575127', 'SWE', '249732'
Union Select '1603575147', 'SWE', '250155'
Union Select '1603575181', 'SWE', '250108'
Union Select '1603575531', 'SWE', '250034'
Union Select '1603575538', 'SWE', '249994'
Union Select '1603575540', 'SWE', '250168'
Union Select '1603575558', 'SWE', '250084'
Union Select '1603575562', 'SWE', '250234'
Union Select '1603575566', 'SWE', '250057'
Union Select '1603575586', 'SWE', '250034'
Union Select '1603575592', 'SWE', '250193'
Union Select '1603575594', 'SWE', '250185'
Union Select '1603575598', 'SWE', '250034'
Union Select '1603575627', 'SWE', '250080'
Union Select '1603575633', 'SWE', '250163'
Union Select '1603575635', 'SWE', '249820'
Union Select '1603575637', 'SWE', '250108'
Union Select '1603575641', 'SWE', '250034'
Union Select '1603575644', 'SWE', '250102'
Union Select '1603575646', 'SWE', '250084'
Union Select '1603575653', 'SWE', '250117'
Union Select '1603575655', 'SWE', '250117'
Union Select '1603575868', 'SWE', '250084'
Union Select '1603575872', 'SWE', '250171'
Union Select '1603575874', 'SWE', '250036'
Union Select '1603575876', 'SWE', '250036'
Union Select '1603575879', 'SWE', '250036'
Union Select '1603575881', 'SWE', '250036'
Union Select '1603575887', 'SWE', '250036'
Union Select '1603575894', 'SWE', '250081'
Union Select '1603575896', 'SWE', '250065'
Union Select '1603575905', 'SWE', '250160'
Union Select '1603575909', 'SWE', '250083'
Union Select '1603575914', 'SWE', '250186'
Union Select '1603575917', 'SWE', '250105'
Union Select '1603575919', 'SWE', '250094'
Union Select '1603575921', 'SWE', '250183'
Union Select '1603575966', 'SWE', '250104'
Union Select '1603575976', 'SWE', '250124'
Union Select '1603575984', 'SWE', '250199'
Union Select '1603575992', 'SWE', '250501'
Union Select '1603575998', 'SWE', '250170'
Union Select '1603576000', 'SWE', '250034'
Union Select '1603576183', 'SWE', '250095'
Union Select '1603576188', 'SWE', '250114'
Union Select '1603576192', 'SWE', '250159'
Union Select '1603576209', 'SWE', '250129'
Union Select '1603576268', 'SWE', '250176'
Union Select '1603576286', 'SWE', '250130'
Union Select '1603576294', 'SWE', '249715'
Union Select '1603576296', 'SWE', '249716'
Union Select '1604576489', 'SWE', '250135'
Union Select '1604576823', 'SWE', '250036'
Union Select '1604576884', 'SWE', '250039'
Union Select '1604576909', 'SWE', '250145'
Union Select '1604576913', 'SWE', '249922'
Union Select '1604576944', 'SWE', '249682'
Union Select '1604577069', 'SWE', '249717'
Union Select '1604577112', 'SWE', '250082'
Union Select '1604577121', 'SWE', '250234'
Union Select '1604577133', 'SWE', '250234'
Union Select '1604577137', 'SWE', '250234'
Union Select '1604577139', 'SWE', '250234'
--SQL Stored procedure to get invoices(@Dbg is to get one invoice only)
/****** Object: StoredProcedure [dbo].[TKL_GetSavedInvoices] Script Date: 2018-06-21 15:23:51 ******/
DROP PROCEDURE [dbo].[TKL_GetSavedInvoices]
GO
/****** Object: StoredProcedure [dbo].[TKL_GetSavedInvoices] Script Date: 2018-06-21 15:23:51 ******/
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
CREATE procedure [dbo].[TKL_GetSavedInvoices]
@ShipmentId nvarchar(50)
,@OfficeId nvarchar(255) = null
,@Dbg smallint = 0
as
begin
declare @InvoiceNo nvarchar(50)
if Exists(Select 1 From TKL_Invoices with(readuncommitted)
where ShipmentId = @ShipmentId
and OfficeId = isnull(@OfficeId, OfficeId)
)
begin
If(@dbg = 0)
begin
select
InvoiceNo
from TKL_Invoices with(readuncommitted)
where ShipmentId = @ShipmentId
and OfficeId = isnull(@OfficeId, OfficeId)
order by Head desc
OPTION ( OPTIMIZE FOR UNKNOWN )
End Else
begin
select
@InvoiceNo = InvoiceNo
from TKL_Invoices with(readuncommitted)
where ShipmentId = @ShipmentId
and OfficeId = isnull(@OfficeId, OfficeId)
order by Head desc
OPTION ( OPTIMIZE FOR UNKNOWN )
Select @InvoiceNo as InvoiceNo
End
End Else
begin
Select 'NA' as InvoiceNo
End
end
GO
【问题讨论】:
-
对于 1) 你是在运行一个脚本,然后是另一个,然后是另一个(即 100 个单独的
Executes)?还是您只点击一次Execute? -
是 100 次执行动态查询以获取发票。
-
运行 100 个查询是问题所在。为什么不能使用单个查询来代替?我知道你说这是使用网络服务,但我会创建一个新的网络方法来获取所有数据,而不是调用 100 次。并小心使用未提交的读取。尤其是发票之类的东西。它不是一个神奇的快速按钮,它带有一些非常严重的包袱。诸如随机重复和/或丢失行之类的事情。还有一大堆其他“有趣”的东西。 blogs.sentryone.com/aaronbertrand/bad-habits-nolock-everywhere
-
尝试将 OPTION ( OPTIMIZE FOR UNKNOWN ) 添加到“如果存在”查询中
-
现在测试了,没有区别。
标签: c# sql-server performance azure tsql