正确使用SqlConnection对象,兼谈数据库连接池

作者: JeffWong

今晚看到上了评论头条的LoveJenny写的为什么要关闭数据库连接,可以不关闭吗?,文章写得简单易懂非常不错,而且代码贴的很到位,下面的讨论也很激烈(老赵都跑过去留言两次了,恨)。又查看了两遍原文中的代码,同意评论中有几位的看法,真正造成多线程并行操作数据库时的连接问题可能是由于对SqlConnection的不当使用。为什么呢?再来看一下LoveJenny兄弟贴出的一段重要源码:

           string sqlConnString = @"Data Source=.\SQLEXPRESS;
                AttachDbFilename=""E:\DB\NORTHWND.mdf"";
                Integrated Security=True;Connect Timeout=30;User Instance=True";
            SqlConnection conn = new SqlConnection(sqlConnString);
            conn.Open();
            Parallel.For(1, Int32.MaxValue, (id) =>
            {
                ExecuteCommand(conn, id);
            });

其中,ExecuteCommand的具体实现如下:

        private static object syncObj = new object();
        private static void ExecuteCommand(SqlConnection conn, int id)
        {
            lock (syncObj)
            {
                if (conn.State != ConnectionState.Open)
                {
                    conn.Open();
                }
                Console.WriteLine("正在执行." + id);
                Thread.Sleep(100);
                SqlCommand cmd = new SqlCommand(
                    string.Format("Insert into Nums values('') ", id), conn);
                cmd.ExecuteNonQuery();
            }
        }

代码很简洁,但是很多人包括自己忍不住都会有几个重大疑问: 1、怎么并行操作n多次数据库只共用一个连接对象呢? 2、并行处理的地方加了锁,每次进行数据库操作都要Lock一下(感觉这根本没有发挥多线程并行处理的优势,个人认为还不如单线程执行的快呢),这个真的有这个必要吗? 3、同一个数据库连接字符串,使用数据库连接对象SqlConnection怎么还要传参呢,显式Open和Close不是更好吗?通常不都是using一下完事吗?

经过简单思考之后,在本地机器上改进了一下实现代码进行测试,如下:

          Parallel.For(1, Int32.MaxValue, (id) =>
            {
                ExecuteCommand(id);
            });

ExecuteCommand方法不再接受SqlConnection对象作为参数,去掉Lock,数据库操作看上去就像是操作一次数据库,打开一次数据库连接,保证线程安全:

        private static void ExecuteCommand(int id)
        {
            using (SqlConnection conn = new SqlConnection(sqlConnString))
            {
                conn.Open();
                Console.WriteLine("正在执行." + id);
                Thread.Sleep(100);
                SqlCommand cmd = new SqlCommand(
                    string.Format("Insert into Nums values('') ", id), conn);
                cmd.ExecuteNonQuery();
            }
        }

当然ExecuteCommand也可以通过显式Open和Close处理:

        private static void ExecuteCommand(int id)
        {
            SqlConnection conn = new SqlConnection(sqlConnString);
            conn.Open();
            Console.WriteLine("正在执行." + id);
            Thread.Sleep(100);
            SqlCommand cmd = new SqlCommand(
                string.Format("Insert into Nums values('') ", id), conn);
            cmd.ExecuteNonQuery();
            conn.Close();
        }

根据的本地机器测试,上面改进后的两种写法数据库连接对象使用正常,线程安全,而且在性能上比加锁操作数据库提高显著,有兴趣的你也不妨一试。

但是,本文的目的不在说代码如何改进才能线程安全提升性能,而是要说明一个非常简单的问题,如何正确合理地使用数据库连接对象SqlConnection?

那么到底如何正确合理地使用数据库连接对象SqlConnection?

最经典的说法莫过于就是using一下即可,这种说法其实是非常正确的,MSDN的使用方法通常也都是using一下,很普遍很潇洒:

SqlConnection      private static void OpenSqlConnection()
        {
            string connectionString = GetConnectionString();
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();
                Console.WriteLine("ServerVersion: {0}", connection.ServerVersion);
                Console.WriteLine("State: ", connection.State);
            }
        }
        static private string GetConnectionString()
        {
            // To avoid storing the connection string in your code, 
            // you can retrieve it from a configuration file, using the 
            // System.Configuration.ConfigurationSettings.AppSettings property 
            return "Data Source=(local);Initial Catalog=AdventureWorks;"
                + "Integrated Security=SSPI;";
        }

其实们都知道SqlConnection对象一定继承自IDispose接口,然后才可以using。using使用的对象最后都会Dispose处理掉,难道数据库连接对象的Close方法和Dispose是等价的?

这里明确告诉您,通过显式Close和using后Dispose是不一样的。到这里是不是很想知道Dispose和Close的区别? MSDN对Close方法的解释是这样的: 关闭与数据库的连接,这是关闭任何打开连接的首选方法。 如果 SqlConnection超出范围,则不会将其关闭。因此,必须通过调用 Close 或 Dispose 显式关闭该连接。Close 和 Dispose 在功能上等效。如果连接池值 Pooling 设置为 true 或 yes,则基础连接将返回到连接池。另一方面,如果 Pooling 设置为 false 或 no,则会关闭到服务器的基础连接。

们可以这样理解:通过调用Close方法关闭的SqlConnection对象,最后是要放回到连接池中的,也就是说这个对象还可以继续使用,只是这个对象现在的连接状态是关闭的,等到下次向池中申请连接对象,这个对象就可以激活复用而不用去new一个新的对象。

而通过调用Dispose()方法,则该对象很可能调用了GC.SuppressFinalize(this)(某些特殊的类可能并没有调用该方法),这样GC就会优先对这些对象进行回收而可能造成该对象无法使用。这里们只要理解,在SqlConnection对象上调用Dispose方法,则这个连接就不能在连接池中继续使用了。

最后说明一下,ado.net使用了连接池技术用来复用连接,减少系统反复打开关闭连接的开销来提升性能,所以上面说通过using"数据库操作看上去就像是操作一次数据库,打开一次数据库连接,保证线程安全"其实也是不对的。下面摘录一段MSDN的说法解惑: 连接池使新连接必须打开的次数得以减少。池进程保持物理连接的所有权。通过为每个给定的连接配置保留一组活动连接来管理连接。每当用户在连接上调用 Open 时,池进程就会查找池中可用的连接。如果某个池连接可用,会将该连接返回给调用者,而不是打开新连接。应用程序在该连接上调用 Close 时,池进程会将连接返回到活动连接池集中,而不是关闭连接。连接返回到池中之后,即可在下一个 Open 调用中重复使用。 只有配置相同的连接可以建立池连接。ADO.NET 同时保留多个池,每种配置各一个。在使用集成的安全性时,连接按照连接字符串以及 Windows 标识分到多个池中。还根据连接是否已在事务中登记来建立池连接。 池连接可以显著提高应用程序的性能和可缩放性。默认情况下,在 ADO.NET 中启用连接池。除非显式禁用,否则,在应用程序中打开和关闭连接时,池进程会对连接进行优化。还可以提供几个连接字符串修饰符来控制连接池的行为。

们可以显式对连接池进行一些操作,但是通常不建议这么做。对连接池您还有什么疑惑吗?欢迎讨论。

参考: http://msdn.microsoft.com/zh-cn/library/system.data.sqlclient.sqlconnection(VS.80).aspx http://msdn.microsoft.com/zh-cn/library/8xx3tyca(VS.90).aspx



原文创作:JeffWong

原文链接:https://www.cnblogs.com/jeffwongishandsome/archive/2011/08/09/2133114.html

更多推荐

更多
  • Azure数据工程指南-二十四、数据治理的权限 创建 azure 预览帐户,探索 azure 预览,探索词汇表,浏览资产,以编程方式使用预览,摘要,管理凭证和访问,创建扫描, 许多组织需要建立数据治理流程、标准和方法,并且已经能够使用内部 SQL Server 工具(如 Master
    Apache CN

  • Azure数据工程指南-二十二、Synapse 分析工作区 创建 Synapse 分析工作区,使用 Spark 探索样本数据,用 SQL 查询数据,用 SQL 创建外部表,摘要, 微软 Azure 数据平台的众多新增功能已经围绕许多类似的产品及其在现代 Azure 数据平台中的用途产生了兴奋和困
    Apache CN

  • Azure数据工程指南-二十三、数据块中的机器学习 创建 MLflow 实验,安装 MLflow 库,创建笔记本,选择性测井,自动记录,摘要, 寻求利用机器学习(ML)和人工智能能力的组织和开发人员花费大量时间构建 ML 模型,并寻求一种方法来简化他们的机器学习开发生命周期,以跟踪实验,
    Apache CN

  • Azure数据工程指南-二十一、将 Apache Spark 的 GraphFrame API 用于图形分析 安装 JAR 库,加载新数据表,将数据加载到数据块笔记本中,用顶点和边构建一个图,查询图表,寻找有图案的图案,用 PageRank 发现重要性,探索入度和出度度量,摘要,进行广度优先搜索,查找连接的组件, 图形技术使用户能够以图形的形式
    Apache CN

  • Azure数据工程指南-20 二十、部署 SQL 数据库先决条件,创建 Visual Studio SQL 数据库项目,安装 Visual Studio GitHub 扩展,导入 AdventureWorks 数据库,连接到 GitHub Repo 源代码控制,将
    Apache CN

  • Azure数据工程指南-十九、部署数据工厂更改 先决条件,创建 DevOps 持续集成构建管道,创建 DevOps 持续部署发布渠道,验证部署的数据工厂资源,摘要,Azure PowerShell 任务停止触发器,ARM 模板部署任务,Azure PowerShell 任务启动触发器
    Apache CN

  • Azure数据工程指南-十八、用于 Cosmos DB 的 Azure Synapse 链接 创建一个 Azure Cosmos DB 帐户,启用 Azure Synapse 链接,创建一个 Cosmos DB 容器和数据库,将数据导入 Azure Cosmos DB,在 Azure Synapse Analytics 中创建
    Apache CN

  • Azure数据工程指南-十六、流分析异常检测 先决条件,创建流分析输入和输出,创建实时电源 BI 仪表板,监控实时电源 BI 流,摘要,创建 Azure 流分析作业,创建物联网中心,创建 Power BI 服务,下载设备模拟器,添加流输入,添加流输出,编写流分析查询,启动流分析作业
    Apache CN

  • Azure数据工程指南-十七、使用 Apache Spark 的实时物联网分析 先决条件,创建物联网中心,创建数据块集群,安装 Maven 库,创建笔记本并运行结构化流查询,摘要,配置笔记本连接,开始结构化流,启动物联网设备模拟器,显示实时流数据,创建 Spark SQL 表,将流写入增量表, 实时物联网分析、高级
    Apache CN

  • Azure数据工程指南-十五、DeltaLake 为什么是酸性 DeltaLake,先决条件,创建并插入 DeltaLake,更新 DeltaLake,从 DeltaLake 删除,浏览增量日志,摘要,插入,更新,删除, 在使用 Azure Data Lake Storage Gen2
    Apache CN

  • 近期文章

    更多
    文章目录

      推荐作者

      更多