最近进行了一次系统性能测试,处理并发操作时出现了许多问题,下面就这些问题及解决途径进行一下记录。
以下问题解决大部分来自于百度、CSDN、博客园等文章及社区,发现真的是好强大。
并发500条,测试。
最开始,并发失败loadrunner报错,服务器连接失败。
猜测可能是数据库连接数出了问题,打开SQL Server,查看数据库最大连接数已经为0,即默认最大。
看了一下IIS最大并发连接数,也是最大了。
百度一下,发现有可能是程序的连接数做了限制,经过查找,了解了SQL的连接字符串的相关属性。
SQL Server 数据库连接字符串参数一览表
|
名称
|
别名
|
默认值
|
允许值
|
说明
|
Application Name
|
无
|
.Net SqlClient Data Provider
|
任意字符串
|
应用程序的明称
|
AttachDBFileName
|
exetended properties (扩展属性), Initial File Name (初始文件名)
|
无
|
任意路径
|
可关联的数据库文件的完整路径
|
Connect Timeout
|
|
15
|
0~32567
|
一个到服务器的连接在终止之前等待的时间长度(以秒计)
|
Connection Timeout
|
|
0
|
0~32567
|
连接生存时间,当一个连接被返回到连接池时,它的创建时间会与当前时间进行对比。如果这个时间跨度超过了连接的有效期的话,连接就被取消。
|
Data Source(数据源)
|
Server(服务器),Address(地址),Addr(地址), Network Address(网络地址)
|
无
|
服务器名称或网络地址
|
SQL Server实例的名称或网络地址。
|
Encrypt
|
无
|
FALSE
|
true, yes, no
|
指定是否对所有在客户和服务器之间传输的数据使用SSL加密
|
Initial Catalog
|
DataBase
|
无
|
服务器上的任何数据库
|
数据库名称
|
Integrated Security (集成安全)
|
Trusted_Connection (受信连接)
|
FALSE
|
true,false, yes,no,sspi
|
指定身份验证的模式。表示Windows认证是否被用来连接数据库。它可以被设置成真、伪或者是和真对等的sspi,其缺省值为伪。
|
Network Library (网络库)
|
Net(网络)
|
dbmssocn
|
dbnmpntw, dbmsrpcn, dbmsadsn, dbmsgnet, dbmslpcn, dbmsspxn, dbmssocn
|
Network.dll。用来建立到一个SQL Server实例的连接的网络库。支持的值包括: dbnmpntw (Named Pipes)、dbmsrpcn (Multiprotocol/RPC)、dbmsvinn(Banyan Vines)、dbmsspxn (IPX/SPX)和dbmssocn (TCP/IP)。协议的动态链接库必须被安装到适当的连接,其缺省值为TCP/IP。
|
Packet Size
|
无
|
8192
|
512的整数倍
|
用来和数据库通信的网络数据包的大小(字节)
|
Password
|
PWD
|
无
|
任意字符串
|
不使用Windows身份验证时的口令
|
Persist Security Info (保持安全信息)
|
无
|
FALSE
|
true,false, yes,no
|
指定是否在连接后传送回敏感信息
|
User ID
|
UID
|
无
|
无
|
不使用Windows身份验证时的用户名
|
Workstation ID
|
无
|
本地计算机名
|
任意字符串
|
连接SQL Server的工作站
|
Connection Reset (连接重置)
|
无
|
TRUE
|
FALSE
|
表示一个连接在从连接池中被移除时是否被重置。一个伪的有效在获得一个连接的时候就无需再进行一个额外的服务器来回运作
|
Current Language (当前语言)
|
无
|
无
|
任意字符串
|
SQL Server语言记录的名称。
|
Enlist(登记)
|
无
|
TRUE
|
FALSE
|
表示连接池程序是否会自动登记创建线程的当前事务语境中的连接
|
Max Pool Size(连接池的最大容量)
|
无
|
100
|
任意整型数值
|
连接池允许的连接数的最大值
|
Min Pool Size(连接池的最小容量):
|
无
|
0
|
任意整型数值
|
连接池允许的连接数的最小值
|
Pooling(池)
|
无
|
TRUE
|
FALSE
|
确定是否使用连接池。如果值为真的话,连接就要从适当的连接池中获得,或者,如果需要的话,连接将被创建,然
|
发现应该是这个Max Pool Size参数导致的,默认100个,所以第101个就连接不上数据库了,测了一下
public static void TestSQLServerConnectionCount()
{
try
{
int maxCount = 200;
string connectionString =
"Data Source=localhost;Initial Catalog=EveryDayTest;User Id=sa;Password=sa123;Max Pool Size=100";
for (int i = 1; i < maxCount; i++)
{
//connectionString += " ";
var db = new SqlConnection(connectionString);
db.Open();
Console.WriteLine("已创建连接对象" + i);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
果然是这么回事,而且此时只能连接100次,而连接字符串变化的情况,即每次的连接字符串都不一样,哪怕多一个空格,最后发现是可以“打破最大连接数的限制”的(是不是因为每种连接字符串都会新建一个连接池?)
先看看MSDN是怎么说的吧
https://msdn.microsoft.com/zh-cn/library/8xx3tyca.aspx
括号的答案是
正确的。Max Pool Size是指连接池的最大连接数,而修改了连接字符串会建立不同的池。
解决了这个问题再次测试,一个更大的问题来了——死锁!!!
通过使用
SQL Server Profiler监测,查到了这个个死锁。
简单说说这个插件的使用:
进入这个页面,选择一个空白模板,在事件选择中,点击Locks节点,勾选Deadlock graph、Lock:Deadlock、Lock:Deadlock Chain
点击列筛选器,选中DataBaseName填写类似于“你要操作的数据库名”进行过滤
然后运行即可。
还是先了解一下锁的相关机制吧。
锁的种类:共享锁(Shared Lock)、更新锁(Update Lock)、排他锁(独占锁、Exclusivel Lock)、意向锁(Intent Lock)、计划锁(Schema Locks)
这篇文章讲解的很是细致,可以参考:
http://blog.csdn.net/yuanyuanispeak/article/details/52756167
回头我们来看
,
上面这个死锁错误是进程59(select)请求非聚集索引下的S锁被进程54持有(update,排他锁),同时54请求非聚集索引下的S锁也被59持有(update,排他锁)。
说白了,都准备执行查询,没想到对方都在更新,即排他。
问题大致找到了,由于本系统原因,调整代码相关SQL执行顺序、部分问题在SQL中使用乐观并发的模式,即加入NoLock,问题基本解决了,死锁问题也OK了。不过找的过程还是比较困难的,用log记录跟踪死锁发生位置,比较费时。(求大神推荐更简单的方法)
为避免或最小化死锁,参考以下建议:
1.确保事务范围足够小
2.使用较低隔离级别的事务
3.对于可能的查询操作,使用NoLock查询提示
4.规范化数据库设计
5.在需要的列上创建索引,这样不需要经常进行整表扫描
6.控制数据库对象访问的顺序是相同的顺序(顺序真的很重要!!!)
万万没想到,死锁的问题都解决了,loadrunner仍然报错,测试100个,总有几个或1个不通过,错误为服务器连接错误,此时说明并不是程序的问题。连接数的问题之前都已经搞定了,为什么?
百度了一下,说可能是负载生成器的性能太好,发数据包特别快,服务器也响应特别快,从而导致负载生成器的机器的端口在没有timeout之前就全部占满了。
在负载生成器的注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters里,有如下两个键值:
TcpTimedWaitDelay
MaxUserPort
1,这里的TcpTimedWaitDelay默认值应该中是30s,所以这里,把这个值调小为5s(按需要调整)。
2,也可以把MaxUserPort调大(如果这个值不是最大值的话)
试了一下,不行啊!
测了50个竟然通过了,与测试人员反复沟通,最后发现,妹的,测试脚本有点小问题,删掉后全通过了。
yes!终于搞定了,真是复杂呀