本章通过研究连接池、群集连接和异步访问等技术来提高程序和数据库通信的效率。
(1) connectionString="server=(Local);database=kcpw;user=sa;pwd=sa";
(2) 写在App.config文件中;
myconnectionstring = String.Format("server={0};database={1};user={2};pwd={3}", server, database, user, pwd);
但是这一种,用户在输入user时还可以插入更多的信息,对数据库有潜在的威胁。如:user=sa;user instance=false。这样,如果这个用户连接数据库后,其他程序或用户就无法再连接数据库了。
上述的各种连接方式都存在一个重要的弊端就是没有对字符串进行检查。如果用户输入错误的关键字,只有到连接时才会发现问题。
解决这些问题的方法是使用ConnectionStringBuilder对象。
下面的代码是连接数据库的标准范例:
App.config:
<connectionStrings>
<clear/>
<add name="Kcpw"
providerName="System.Data.SqlClient"
connectionString="server=(Local);database=kcpw;user=sa;pwd=sa"
/>
</connectionStrings>
代码:
ConnectionStringSettings kcpw = ConfigurationManager.ConnectionStrings["Kcpw"];
DbProviderFactory factory = DbProviderFactories.GetFactory(kcpw.ProviderName);
DbConnectionStringBuilder bld = factory.CreateConnectionStringBuilder();
bld.ConnectionString = kcpw.ConnectionString;
DbConnection conn = factory.CreateConnection();
conn.ConnectionString = bld.ConnectionString;
DbDataAdapter adapter = factory.CreateDataAdapter();
DbCommand cmd = factory.CreateCommand();
cmd.Connection = conn;
cmd.CommandType = CommandType.Text;
cmd.CommandText = "Select top 10 * from kemu";
adapter.SelectCommand = cmd;
DbCommandBuilder cmdbld = factory.CreateCommandBuilder();
cmdbld.DataAdapter = adapter;
DataSet ds = new DataSet();
adapter.Fill(ds, "kemu");
dataGridView1.DataSource = ds;
dataGridView1.DataMember = "Kemu";
创建和打开数据存储区连接是一项十分耗时和占用资源的任务。解决这个问题的方法是在启动应用程序时立即打开一个数据存储区的连接,并一直保持到程序关闭,但是这种长期保持连接的方法大多数时候并不适用,因为使用应用程序的用户并不是时时刻刻都在请求数据传输,特别是在多用户系统中这种问题尤为明显。事实上,我们需要数据存储区将大部分时间花在数据传输上,而尽可能只花少量的时间维护连接。这正是连接池的特点。
连接池是一种客户端技术。数据库并不知道应用程序是使用一个连接池还是使用多个连接池。
当有数据库连接请求时,连接管理器首先检查连接池中是否包含可用的连接,有则返回该连接。如果没有,则检查是否达到连接池的最大容量,若没有达到,则创建新的连接,若以达到最大连接数,则将这个请求添加到队列中,并且只要未超过连接所限定的超时时间,就返回下一个可用的连接。
ConnectionTimeout / MinPoolSize(推荐值5) / MaxPoolSize(推荐值100) / Pooling / ConnectionReset / LoadBalancingTimeout or ConnectionLifetime / Enlist
要想实现连接池,则必须遵循:
1) 每个入池的用户或服务都必须使用相同的连接字符串。区分大小写
2) 每个入池的用户或服务都必须使用相同的用户ID
3) 进程ID必须相同。既不能在进程间共享连接,也不能在进程间共享连接池
连接池组是一个为特定ADO.NET提供者管理连接池的对象。在实例化第一个连接时,将会创建一个连接池组对象。然后,只有在打开第一个连接时,才会创建第一个连接池。
在需要使用连接时,会从连接池的可用连接列表中取出一个连接,在使用完毕后,会将该连接返回给连接池的可用连接列表。在一个连接返回到连接池中时,连接池并不会无限期地保存处于空闲状态的连接,而是会随机设置一个空闲生命期时间(在4分钟到8分钟)。如果希望应用程序在长时间处于空闲状态时,确保至少有一个连接可用,则可以MinPoolSize设置为1或一个稍微大一点的值。
该参数一般只在多个群集服务器的环境中使用,它可以帮助平衡数据库连接负载。只有在关闭连接时,才会检查该设置。如果连接的打开时间超过其LoadBalancingTimeout的设置值,则将销毁该连接;否则将其返回至连接池。
默认值是100。但是如何确定一个比较好的最大容量值呢?通常要借助于性能监测器。在大型Web应用程序中,通常会出现连接泄漏的问题(忘记关闭连接后程序出现异常是最常见的原因)。
using (SqlConnection cn = new SqlConnection(cnSettings.ConnectionString))
{
cn.open();
using (SqlCommand cmd = cn.CreateCommand())
{
……
}
}
using块将自动调用Dispose方法。这是在代码中处理连接对象、命令对象和其他任何实现Dispose方法的对象的最佳方式。但是这并部意味着以前的手动调用close方法或dispose方法就没有用,比如说在需要有某种条件下关闭时,这时候就是手动调用的用武之地了。
通常情况下,连接池都是打开的,但是,如果需要解决与连接相关的问题,则可能要关闭连接池(方法:Pooling参数设置为false)。
在使用一个数据库服务器时,该服务器不一定总是可用的。在数据库服务器不可用时,将无法使用连接池中的连接。
可以在代码中使用以下两个方法恢复一个不可用的连接池:ClearPool方法和ClearAllPools方法。他们是SqlConnection和OracleConnection类的静态方法。如下例,在停止和重启数据库服务之前,该代码片断将一直使用连接池。
public void displayversion()
{
string ver = null;
SqlConnectionStringBuilder cnSettings = new SqlConnectionStringBuilder(@"server=(Local);database=kcpw;user=sa;pwd=sa;Max Pool Size=5");
using (SqlConnection cn = new SqlConnection(cnSettings.ConnectionString))
{
cn.Open();
using (SqlCommand cmd = cn.CreateCommand())
{
cmd.CommandText = "select @@version";
ver = (string)cmd.ExecuteScalar();
}
}
MessageBox.Show(ver);
}
这种使用连接池,开始没有问题,但是停止再开启数据库服务器后,代码就会抛出异常。要想从该异常中恢复,则要首先清除连接池,然后重新执行代码。如下例:
try
{
displayversion();
}
catch (SqlException oe)
{
if (oe.Number != 1236 ) throw oe; //1236是异常号,但测试时好像不对,因此这个号有待进一步查询
System.Diagnostics.Debug.WriteLine("Clearing Pools!");
SqlConnection.ClearAllPools();
displayversion();
}
在准备产品化应用程序的时候,我们要考虑更多关于容错恢复方面的问题和解决的机制。如,我们需要考虑数据库是否能够经受众多用户对应用程序的反复使用?如果数据库服务器关机,会出现什么情况?如果数据库服务器重启,会出现什么情况?如何快速地停止和重启数据库服务?
本节的容错恢复技术讨论的是如何在重启后保证得到的结果与服务器关闭之前的结果完全相同。
如下图:
反映转换后的数据库镜像 |
|
观察者 |
主服务器 |
镜像服务器 |
这时服务器发生了变化,我们需要重新改写数据库连接字符串吗?不需要,我们只需要使用连接字符串中的FailoverPartner设置参数就能达到这一目的。但是这只能用于ADO.Net2.0和SqlServer2005进行协同。 |
用户 |
事务 |
事务 |
你还在吗? |
反映初始角色配置的数据库镜像 |
|
观察者 |
主服务器 |
镜像服务器 |
客户程序一般只和主服务器会话,镜像服务器处于一种连续的备份-恢复操作。 当然,此时用户也可以和镜像服务器会话。 观察者服务器只是查看两个数据服务器的运行情况,它的任务是当主服务器关闭后,自动切换到镜像服务器。 |
用户 |
事务 |
事务 |
你还在吗? |
你还在吗? |
异步数据访问能够极大地提高应用程序的性能(响应性能)。在使用异步访问时,可以同时执行多条命令,并且通过使用轮流检测技术(WaitHandles)方法或委派技术,就能得到命令已完成的通知。
命令一般时同步执行的,其优点就是降低了编码的复杂度,其本质上过程化的。但是,在命令完成之前,程序会一直处于“阻塞”状态,长时间运行的命令会产生性能上的问题。
如果是异步执行的话,则不会阻塞程序的执行,因为异步执行的命令是运行在新的线程上的。虽然增加了编码的复杂度,但是将极大地提高程序的响应性能。
同步执行:
string ver = null;
SqlConnectionStringBuilder cnSettings = new SqlConnectionStringBuilder(@"server=(Local);database=kcpw;user=sa;pwd=sa;Max Pool Size=5");
using (SqlConnection cn = new SqlConnection(cnSettings.ConnectionString))
{
cn.Open();
using (SqlCommand cmd = cn.CreateCommand())
{ cmd.CommandText = "WaitFor Delay '00:00:15' select @@version";
ver = (string)cmd.ExecuteScalar();
}
}
MessageBox.Show(ver);
这种在执行的时间里面,程序不能去执行其他任务。
private void button4_Click(object sender, EventArgs e)
{
SqlConnectionStringBuilder cnSettings = new SqlConnectionStringBuilder(@"server=(Local);database=kcpw;user=sa;pwd=sa;Max Pool Size=5;async=true");
using (SqlConnection cn = new SqlConnection(cnSettings.ConnectionString))
{
cn.Open();
SqlCommand cmd = cn.CreateCommand();
cmd.CommandText = "WaitFor Delay '00:00:08' select @@version";
cmd.BeginExecuteReader(new AsyncCallback(ProcessResult), cmd);
}
}
public void ProcessResult(IAsyncResult ar)
{
SqlCommand cmd = (SqlCommand)ar.AsyncState;
using (cmd.Connection)
{
using (cmd)
{
//string ver = null;
SqlDataReader rdr = cmd.EndExecuteReader(ar);
if (rdr.Read())
{
//ver = (string)rdr[0];
MessageBox.Show("12");
//label1.BeginInvoke(new LabelHandler(UpdateLabel), ver);
}
}
}
}
//public delegate void LabelHandler(string txt);
//public void UpdateLabel(string txt)
//{
// label1.Text = txt;
//}
SQL Server提供者提供内置的运行时统计功能,用于统计一个合法连接对象的数据信息。总共有20多项统计数据可用于帮助用户统计应用程序的执行状况。
如果想使用这些功能,则必须拥有一个合法的连接对象,并启动统计功能。
DataTable authors = new DataTable();
string connstring = @"server=(Local);database=kcpw;user=sa;pwd=sa;Max Pool Size=5;";
using (SqlConnection myconn = new SqlConnection(connstring))
{
using (SqlCommand cmd = myconn.CreateCommand())
{
myconn.Open();
myconn.StatisticsEnabled = true;
cmd.CommandText = "select * from kemu";
authors.Load(cmd.ExecuteReader());
dataGridView1.DataSource = authors;
}
ArrayList stats = new ArrayList(myconn.RetrieveStatistics());
dataGridView2.DataSource = stats;
}