代码的用途非常简单-对于100,000个周期,我需要从SQL Server存储过程中生成一个随机名称和数字,然后将这些值写入表中。之后,从表中读取100个随机行,进行1000次,并且并不真正在乎您遍历100行的操作。
练习的重点是分析加密对应用程序。因此,阅读器循环所执行的操作实际上并不重要,也不需要对其进行优化,因为这只是占用时间,并且在两种情况下都是相同的。还应注意的是,多个连接是有意的-应该进行模拟-无需进入多线程或应用的多个实例-在高度并发的应用中您会看到的所有开销(即使深入了解,它仍然按顺序执行。)
向我指出的项目(我想更好地理解):
for
优于while
顶部的可变声明是错误的
代码Code肿
可移植性很差
我也肯定有更好的方法计时代码的性能,而不是将当前时钟时间转储到控制台。
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
namespace AEDemo
{
class Program
{
static void Main(string[] args)
{
using (SqlConnection con1 = new SqlConnection())
{
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
string name;
string EmptyString = "";
string conString = ConfigurationManager.ConnectionStrings[args[0]].ToString();
int salary;
int i = 1;
while (i <= 100000)
{
con1.ConnectionString = conString;
using (SqlCommand cmd1 = new SqlCommand("dbo.GenerateNameAndSalary", con1))
{
cmd1.CommandType = CommandType.StoredProcedure;
SqlParameter n = new SqlParameter("@Name", SqlDbType.NVarChar, 32)
{ Direction = ParameterDirection.Output };
SqlParameter s = new SqlParameter("@Salary", SqlDbType.Int)
{ Direction = ParameterDirection.Output };
cmd1.Parameters.Add(n);
cmd1.Parameters.Add(s);
con1.Open();
cmd1.ExecuteNonQuery();
name = n.Value.ToString();
salary = Convert.ToInt32(s.Value);
con1.Close();
}
using (SqlConnection con2 = new SqlConnection())
{
con2.ConnectionString = conString;
using (SqlCommand cmd2 = new SqlCommand("dbo.AddPerson", con2))
{
cmd2.CommandType = CommandType.StoredProcedure;
SqlParameter n = new SqlParameter("@LastName", SqlDbType.NVarChar, 32);
SqlParameter s = new SqlParameter("@Salary", SqlDbType.Int);
n.Value = name;
s.Value = salary;
cmd2.Parameters.Add(n);
cmd2.Parameters.Add(s);
con2.Open();
cmd2.ExecuteNonQuery();
con2.Close();
}
}
i++;
}
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
i = 1;
while (i <= 1000)
{
using (SqlConnection con3 = new SqlConnection())
{
con3.ConnectionString = conString;
using (SqlCommand cmd3 = new SqlCommand("dbo.RetrievePeople", con3))
{
cmd3.CommandType = CommandType.StoredProcedure;
con3.Open();
SqlDataReader rdr = cmd3.ExecuteReader();
while (rdr.Read())
{
EmptyString += rdr[0].ToString();
}
con3.Close();
}
}
i++;
}
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
}
}
}
}
#1 楼
如果同时出现两个while
循环,则可以轻松地使它们成为for
循环,这通常是最佳做法。 int i = 1;
while (i <= 100000)
{
// main while code
i++
}
应重写为:
for (int i = 1; i <= 100000; i++)
{
// main while code
}
类似:
i = 1;
while (i <= 1000)
{
// main while code
i++;
}
将成为:
for (int i = 1; i <= 1000; i++)
{
// main while code
}
这使您可以在以后循环使用变量
i
,并且还可以避免在循环之间意外忘记将i
重置回1
。通常,变量i
代表iterator
,并且通常专门为循环保留。这对性能没有实际影响,但对可读性和可维护性有严重影响。使用
for
循环,可以立即清楚迭代器发生了什么。使用while
循环,您必须在while
的主体中进行搜索,以找到在何处操纵迭代器。>顶部的变量声明是错误的
通常对此不满意。您应该声明变量,使其尽可能接近其用法,并在要使用它们的最严格的范围内。例如,
int salary;
string name;
应该在第一个循环中声明(现在应该是
for
循环):for (int i = 1; i <= 100000; i++)
{
int salary;
string name;
}
变量不能在不应使用的地方意外地使用,因此当它们立即超出范围时可以将其回收。这也意味着您不能在应用于它们的块之外意外地使用它们的值。
尝试选择更有效的变量名。除某些公认的缩写外(从
i
到iterator
,e
到eventArgs
等),切勿缩写: using (SqlConnection con1 = new SqlConnection())
{
将更被接受为: br />
using (SqlConnection connection1 = new SqlConnection())
{
这通常是最佳实践。
您可能会直接使用
EmptyString
变量看到巨大的性能下降:string
类型是不可变的,这意味着每次执行EmptyString += rdr[0].ToString();
时,它将使用组合创建字符串的新实例,然后将其分配给EmptyString
,然后进行调度用于垃圾回收的原始字符串。,而是使用
StringBuilder
:StringBuilder emptyStringBuilder = new StringBuilder();
for (int i = 1; i <= 1000; i++)
{
// ...
while (rdr.Read())
{
emptyStringBuilder.Append(rdr[0].ToString());
}
// ...
}
这样可以节省很多性能。有关更多信息,请参见MSDN。
另一个变量命名为nit-pick:您应该始终
camelCase
本地名称和private
变量名称:即emptyString
而不是EmptyString
。此是另一种最佳实践。
我们全部重写后:
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
namespace AEDemo
{
class Program
{
static void Main(string[] args)
{
using (SqlConnection connection1 = new SqlConnection())
{
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
string connectionString = ConfigurationManager.ConnectionStrings[args[0]].ToString();
for (int i = 1; i <= 100000; i++)
{
connection1.ConnectionString = connectionString;
string name;
int salary;
using (SqlCommand command1 = new SqlCommand("dbo.GenerateNameAndSalary", connection1))
{
command1.CommandType = CommandType.StoredProcedure;
SqlParameter nameParameter = new SqlParameter("@Name", SqlDbType.NVarChar, 32) { Direction = ParameterDirection.Output };
SqlParameter salaryParameter = new SqlParameter("@Salary", SqlDbType.Int) { Direction = ParameterDirection.Output };
command1.Parameters.Add(nameParameter);
command1.Parameters.Add(salaryParameter);
connection1.Open();
command1.ExecuteNonQuery();
name = nameParameter.Value.ToString();
salary = Convert.ToInt32(salaryParameter.Value);
connection1.Close();
}
using (SqlConnection connection2 = new SqlConnection())
{
connection2.ConnectionString = connectionString;
using (SqlCommand command2 = new SqlCommand("dbo.AddPerson", connection2))
{
command2.CommandType = CommandType.StoredProcedure;
SqlParameter nameParameter = new SqlParameter("@LastName", SqlDbType.NVarChar, 32);
SqlParameter salaryParameter = new SqlParameter("@Salary", SqlDbType.Int);
nameParameter.Value = name;
salaryParameter.Value = salary;
command2.Parameters.Add(nameParameter);
command2.Parameters.Add(salaryParameter);
connection2.Open();
command2.ExecuteNonQuery();
connection2.Close();
}
}
}
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
// We'll declare the StringBuilder outside the `for` to get maximum performance impact, and demonstrate it's effectiveness
StringBuilder emptyStringBuilder = new StringBuilder();
for (int i = 1; i <= 1000; i++)
{
using (SqlConnection connection3 = new SqlConnection())
{
connection3.ConnectionString = connectionString;
using (SqlCommand command3 = new SqlCommand("dbo.RetrievePeople", connection3))
{
command3.CommandType = CommandType.StoredProcedure;
command3.Open();
SqlDataReader reader = command3.ExecuteReader();
while (reader.Read())
{
emptyStringBuilder.Append(reader[0].ToString());
}
connection3.Close();
}
}
}
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
}
}
}
}
免责声明
我在IDE之外编写了此代码,可能需要进行一些细微的更改才能使其正常工作。
另外,我相信还有更好的方法除了将当前时钟时间转储到控制台之外,还可以对代码的性能进行计时。是的,您应该查看Stopwatch类以进行计时。这比使用打印到
DateTime
上的Console
字符串要精确得多。它还可以让您轻松地重置它,等等,因此您可以构建一个非常有效的诊断过程,以衡量您要尝试的特定性能影响。我还建议您采取以下措施玛拉基(Malachi)关于连接字符串的建议。最后,在注释中进行讨论之后,我建议您构建一个类来表示
dbo.RetrievePeople
的返回值。然后,您应该创建该类的实例而不是emptyStringBuilder.Append
,并将每个实例添加到List<T>
。这将使您能够与实际用例进行更多的性能测试。很高兴在这里看到您,您是DBA网站上的传奇人物! :)
评论
\ $ \ begingroup \ $
谢谢,迭代器的性能根本不是我要优化的。实际上,我故意写得很慢。
\ $ \ endgroup \ $
–亚伦·伯特兰(Aaron Bertrand)
2015年8月12日14:00
\ $ \ begingroup \ $
迭代器的运行速度并不慢,将字符串添加到字符串部分是很慢的。根据您要测量的内容(明智的性能),最好在那里使用StringBuilder,这样就不会因多余资源的创建/回收而产生噪音。
\ $ \ endgroup \ $
– Der Kommissar
15年8月12日在14:01
\ $ \ begingroup \ $
好的,但是我正在尝试使应用程序执行某些操作,即使它很浪费,就像真实应用程序一样。
\ $ \ endgroup \ $
–亚伦·伯特兰(Aaron Bertrand)
15年8月12日在14:04
\ $ \ begingroup \ $
@AaronBertrand问题是,您是否要测试它在应用程序或SQL Server上的性能?如果要对SQL Server进行压力测试,则最好使用StringBuilder,因为这样做会明显更快,然后几乎立即返回SQL Server进行更多工作。 (我认为那是你的目标?)
\ $ \ endgroup \ $
– Der Kommissar
15年8月12日在14:06
\ $ \ begingroup \ $
@AaronBertrand字符串串联具有二次性能损失。这并不代表实际的应用程序。我了解您需要进行一些处理,但是行数应该是线性的。不二次。
\ $ \ endgroup \ $
–usr
15年8月12日在14:28
#2 楼
为什么在using (SqlConnection con1 = new SqlConnection())
循环之前的while
循环中没有using (SqlCommand cmd1 = new SqlCommand("dbo.GenerateNameAndSalary", con1))
? 现在
con1
在第14行被实例化,直到第82行才被处理,但是在第39行之后才被使用。您实际上是在con2
con1
块以及其中的using
内部打开con3
while (i <= 1000)
。此:
SqlParameter n = new SqlParameter("@LastName", SqlDbType.NVarChar, 32);
SqlParameter s = new SqlParameter("@Salary", SqlDbType.Int);
n.Value = name;
s.Value = salary;
cmd2.Parameters.Add(n);
cmd2.Parameters.Add(s);
...可以重写为:
cmd2.Parameters.Add("@LastName", SqlDbType.NVarChar, 32).Value = name;
cmd2.Parameters.Add("@Salary", SqlDbType.Int).Value = salary;
为什么这样做:
using (SqlConnection con2 = new SqlConnection())
{
con2.ConnectionString = conString;
...当
SqlConnection
具有接受连接字符串的构造函数时?由于连接位于
con1.Close();
块内,因此无需执行con2.Close();
,using
等。您还需要将
SqlDataReader
包裹在using块内。评论
\ $ \ begingroup \ $
我不知道如何错过没有using块的SqlDataReader。好答案!
\ $ \ endgroup \ $
– Der Kommissar
15年8月12日在14:24
#3 楼
您应该通过执行以下操作将连接字符串移出using语句之外 using (SqlConnection con1 = new SqlConnection())
{
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
string name;
string EmptyString = "";
string conString = ConfigurationManager.ConnectionStrings[args[0]].ToString();
int salary;
int i = 1;
while (i <= 100000)
{
con1.ConnectionString = conString;
像这样
string conString = ConfigurationManager.ConnectionStrings[args[0]].ToString();
using (SqlConnection con1 = new SqlConnection(conString))
{
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
string name;
string EmptyString = "";
int salary;
int i = 1;
while (i <= 100000)
{
您无需再在代码中的后面的
SqlConnection
对象中设置连接字符串。您也可以省去关闭连接的调用,这在范围离开using语句时自动发生。
我认为与数据库的连接与其余代码无关。实际上,您可以为该应用程序中的每个操作使用相同的连接,它应该使您更好地了解每个操作的处理方式。
我建议不要调用
Console.WriteLine
直到方法结束。我的意思是说,您应该将时间保存在变量中,并在所有乐趣开始之前进行声明,将变量分配给当前具有Console.WriteLine
的位置,然后在完成所有操作后将其写到控制台中。 这将使
Console.WriteLine
繁重的过程从计算这些过程的时间的方程式中脱离出来。还有一种方法可以使用围绕
SqlDataReader
的声明。 您必须创建阅读器,然后将其放入这样的using语句中。
SqlDataReader rdr = cmd3.ExecuteReader();
using (rdr)
{
while (rdr.Read())
{
EmptyString += rdr[0].ToString();
}
}
它与命令或命令稍有不同。连接,但数据读取器仍实现IDisposable接口,并且在使用中发生任何事情时都应正确处理。
就循环而言,for循环将看起来更整洁,并且更易于维护,如果以后需要在代码中进行一些更改。
在for循环声明中声明增量变量
在for循环声明中处理句柄增量
处理for循环声明中的循环数
使用while循环时,您只能在声明内部执行其中之一,其余操作必须在代码中的其他位置完成,并且可能导致意外的增量或其他更改增量变量的操作。
使用for循环,递增变量的作用域为该循环,并在作用域离开for循环时销毁。在while循环中,在退出while循环后,增量变量仍然可用,并且您不需要访问该值,为什么要这么做?
评论
\ $ \ begingroup \ $
您能解释一下为什么更好吗?记住,我想连接到数据库100,000次,而不是一次,因此握手开销是配置文件的一部分。
\ $ \ endgroup \ $
–亚伦·伯特兰(Aaron Bertrand)
15年8月12日在13:34
\ $ \ begingroup \ $
@AaronBertrand将conString移到SqlConnection的构造函数将不会连接到数据库。它仅节省您在将该变量重置为完全相同的内容100,000次时所浪费的资源。
\ $ \ endgroup \ $
– Der Kommissar
15年8月12日在13:37
\ $ \ begingroup \ $
对于连接字符串,实际上使用了不同的连接,我只是略作简化。对于console.writeline东西,您可以假定与数据交互的实际应用程序实际上可以完成工作。由于在所有情况下向控制台写入日期时间的成本都应该相同,因此是否要在所有测试中始终包含额外的时间,或者从未在任何测试中包含额外的时间,这真的很重要吗?我正在尝试衡量相对表现,而不是绝对。
\ $ \ endgroup \ $
–亚伦·伯特兰(Aaron Bertrand)
15年8月12日在13:49
\ $ \ begingroup \ $
您应该发布与实际代码最接近的代码,否则您将获得与实际代码不符的评论。
\ $ \ endgroup \ $
–马拉奇♦
15年8月12日在13:51
\ $ \ begingroup \ $
安顿下来,我只是合并了两个单独的conString变量。
\ $ \ endgroup \ $
–亚伦·伯特兰(Aaron Bertrand)
15年8月12日在14:15
#4 楼
for
循环您可以使用
while
循环,而不是使用i
循环,并且具有迭代器变量for
。一个简单的for
循环的结构如下:for(/* Declare `i` */; /* Check the value of `i` against a condition */; /* Change the value of `i` */)
{
...
}
例如,此
while
循环在代码开头附近:int i = 1;
while (i <= 100000)
{
...
}
将成为以下
for
循环for(int i = 1; i <= 100000; ++i)
{
}
通过使用
for
循环,您可以无需声明i
并对其进行递增重复代码的分离
我注意到您将这一行代码重复了三遍:
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
虽然在像这样的小程序中,这没什么大不了的,但我还是建议将其封装在如下的辅助方法中:
private void CurrentUtcDate()
{
Console.WriteLine(DateTime.UtcNow.ToString("hh:mm:ss.fffffff"));
}
然后您可以将此方法添加到
Program
类中,并调用this.CurrentUtcDate()
或CurrentUtcDate()
以达到相同的效果。连帽衫
我看不到需要我挑剔的与此代码相关的许多事情,但是以下几项可以更改:
添加s
Main
方法中的一些空白行。这将有助于提高可读性。还要在其中插入一些内联注释
//
,解释某些代码块的功能以及它们的工作方式。删除代码顶部无用的
using
语句,就像System.Collections.Generic
和System.Text
一样。评论
\ $ \ begingroup \ $
如果删除了System.Configuration,则为IIRC,我无法从App.Config中提取ConfigurationManager.ConnectionStrings。同时->是否需要进行有意义的更改(考虑到我的大多数基于SQL Server的读者比while更加熟悉),还是更多地进行了微优化?
\ $ \ endgroup \ $
–亚伦·伯特兰(Aaron Bertrand)
2015年8月12日在13:44
\ $ \ begingroup \ $
CurrentUtcDate()的描述性不是很好,并且具有意外的副作用。我会使用类似PrintCurrentUtcDate()或PrintDate(DateTime date)的方法,然后调用PrintDate(DateTime.UtcNow)。
\ $ \ endgroup \ $
– Dorus
15年8月13日在12:36
评论
出于好奇,这平均需要执行多长时间?@EBrown进行了5次以上的平均值:cdn.sqlperformance.com/wp-content/uploads/2015/08/AE-Perf.png
我想这个标题还可以吗?
大声笑@AaronBertrand,您应该在聊天中与他讨论。但是该标题非常简短地说明了代码的含义。
@Malachi我的原标题也是如此。