DotNet?Com ?????????????
Shared by: bhzz42
-
Stats
- views:
- 49
- posted:
- 4/27/2012
- language:
- Chinese
- pages:
- 47
Document Sample


1.分布式处理的环境配置问题
一、问题现象
假如分布式事务的客户端和服务器端(可能 N 个)不在同一台服务器上,如分别为应用程序服
务器 和 数据库服务器,经常会出现下面的错误:
① 在建立与服务器的连接时出错。在连接到 SQL Server 2005 时,在默认的设置下 SQL
Server 不允许进行远程连接可能会导致此失败。 (provider: 命名管道提供程序, error: 40 -
无法打开到 SQL Server 的连接)。
② 事务已被隐式或显式提交,或已终止。
③ 该伙伴事务管理器已经禁止了它对远程/网络事务的支持。 (异常来自
HRESULT:0x8004D025)。 (TransactionScope 异常)
④ [COMException (0x8004d00e): 此 事 务 已 明 地 或 暗 地 被 确 认 或 终 止 ( 异 常 来 自
HRESULT:0x8004D00E)]。(MSDTC 分布式事务错误)
⑤ Import of MSDTC transaction failed: Result Code = 0x8004d023. (MSDTC 安全性配置问题)
二、解决方法
遇到以上的问题或 SQL Server 分布式的问题,请按照以下步骤设置,问题应该可以得到解决。
可能有些步骤对您来说是多余的,但求全不求漏。
1. 启动 MSDTC 服务。
MSDTC 简介:MSDTC 是 Microsoft Distributed Transaction Coordinator 的简称,即微软分布式
事务协调器,描述:协调跨多个数据库、消息队列、文件系统等资源管理器的事务。如果停
止次服务, 则不会发生这些事务。 如果禁用此服务, 显式依赖此服务的其他服务将无法启动。
MSDTC 启动方法:
①“开始”|“运行” ,输入“services.msc” ,或者“控制面板”|“管理工具”|“服务” ,打
开“服务”窗口,在名称中找到“Distributed Transaction Coordinator” ,将其启动。
②“开始”|“所有程序”|“Microsoft SQL Server”|“服务管理器” ,打开“SQL Server 服
务管理器”窗口,选中“Distributed Transaction Coordinator”服务,将其启动。
2. 设置 MSDTC 组件。
① 检查操作系统是否安装 DTC 组件。XP 默认安装,Win2003 默认不安装。安装步骤如下:
a. “开始”|“控制面板”|“添加/删除程序”|“添加/删除 Windows 组件” ,选择“应用
程序服务器” ,单击“详细信息” ,选择“启用网络 DTC 访问” ,单击“确定”|“下一步”|
“完成” 。
b. 停止并重启 MSDTC 服务(命令:net stop msdtc 和 net start msdtc)。
c. 停止参与分布式事务的任何资源管理器服务(如 Microsoft SQL Server 或 Microsoft
,然后重新予以启动。
Message Queue Server)
② “开始”|“运行” ,输入“dcomcnfg”,或者“控制面板”|“管理工具”|“组件服务” ,
打开“组件服务”窗口, “组件服务”|“计算机”|“我的电脑” ,右键“属性”|“MSDTC”,
勾选“使用本地协调器” ,单击“安全性配置” ,弹出“安全配置”窗口,勾选“网络 DTC
访问” “允许远程客户端”
、 “允许远程管理”
、 、
“允许入站” “允许出站”
、 “不要求进行验证”
、 、
“启用事务 Inernet 协议(TIP)事务”和“启用 XA 事务”(“允许入站”和“允许出站”据
具体情况设置,客户端机器必须“允许出站” ,服务器端机器必须“允许入站”),DTC 登陆
账号必须是“NT AUTHORITY"NetworkService” 。Vista 系统只需勾选“使用本地协调器”即可。
③ 配置防火墙以允许与 Msdtc.exe 服务的网络通讯。 【测试时可以退掉所有的防火墙和杀毒
软件】
3. 检查两台机器能够相互 ping 通 Hostname。
注意是机器名,而不是 IP。若不可以,进行如下设置:
① 在目录“%windir%"system32"drivers"etc”下的 hosts 文件,写上 IP 和 HostName 的对应
信息,如 “10.25.11.185 GSMAJK” 。另外,你也可以使用 DTCping 工具来测试是否可以
在 两 台 机 器 之 间 使 用 MSDTC, 并 帮 你 分 析 原 因 , 下 载 地 址 可 以 到 微 软 的 站 点
http://download.microsoft.com/download/complus/msdtc/1.7/nt45/en-us/DTCPing.exe 。
4. 启动 SQL Server 的分布式远程连接。
分布式事务如果涉及到多个机器上的 SQL 实例,则在每个机器上都要做如下的设置。
在.net 中,如果在一次事务中只涉及一个数据库连接,则会自动采用耗用资源较少的轻量级
事务,此时,事务由此连接相应的 RM 控制。如果再增加一个连接,才会提升到 DTC 事务,
这时,事务会由一个 TM 控制。如果配置不好,则会引起事务提升失败。
(1) SQL Server 2000:
① “开始”|“所有程序”|“Microsoft SQL Server”|“企业管理器” ,鼠标选中 SQL Server 实
例,如:GSMAJK(Windows NT),右键“属性” ,弹出“SQL Server 属性(配置)”窗口, “连
接”|“远程服务器连接” ,选中“允许其他 SQL Server 使用 RPC 远程连接到本 SQL Server”
”
和“强制分布式事务处理(MTS),SQL Server 2000 默认情况下是选中的。
【测试时可以退掉所有的防火墙
② 配置防火墙以允许与 SQL Server 服务相关的网络通讯。
和杀毒软件】
(2) SQL Server 2005:参见如何配置 SQL Server 2005 以允许远程
连接
① 在您要从远程计算机连接到的 SQL Server 实例上启用远程连接。
“开始”|“所有程序”|“Microsoft SQL Server 2005”|“配置工具”|“SQL Server 外围应
用配置器” ,单击“服务和连接的外围应用配置器”|“数据库引擎” ,单击“远程连接” ,选
中“本地连接和远程连接”和“同时使用 TCP/IP 和 named pipes” ,然后单击“应用” 。在接
收到消息“直到重新启动数据库引擎服务后, 对连接设置所做的更改才会生效” 单击 后, “确
定”。单击“服务” ,检查“MSSQLSERVER 服务”是否启动。
② 打开 SQL Server Browser 服务。
“开始”|“所有程序”|“Microsoft SQL Server 2005”|“配置工具”|“SQL Server 外围应
用配置器” ,单击“服务和连接的外围应用配置器”|“SQL Server Browser” ,在“启动类型”
中单击“自动”选项,然后单击“应用” 单击“启动” 。 ,然后单击“确定” 。
③ 配置防火墙以允许与 SQL Server 和 SQL Server Browser 服务相关的网络通讯。 【测试时
可以退掉所有的防火墙和杀毒软件】
5. SQL Server 是否安装必须的补丁。
如 SQL Server 2000 的 SP4。
6.检查你的两台服务器是否在同一个域中.
如果不在同一个域中,是否建立可信任联接.
可以在同一工作组中。
7.如果是 WIN2000,升级到 SP4
8.升级 MDAC 到 2.6 以上,最好是 2.8.
在一次.NET 开发中,问题现象中①②③条全遇到了,弄了好几天都没有解决,后来,查看
数据库版本,一个是 SQL Server 2000 SP3,一个是 SQL Server 2000 RTM,我就在后者的基础
上安装了 SQL Server 2000 SP4,结果问题解决。
2.如何在 VS2005 中调试 COM+组件
来源: http://support.microsoft.com/kb/919519/zh-cn?spid=3041&sid=88
概要
Microsoft Visual Studio 2005 或使用 Microsoft Visual Studio.net 来调试 COM + 组件。此外,
本文描述
1. 客户程序与 COM+组件在同一计算机上的调试, 这是本地的 COM + 组件。
2. 当客户程序与 COM+组件在不同计算机上的调试. 这是远程的 COM + 组件。
此外,COM + 组件可以使用非托管的代码或托管 (Microsoft.net) 代码。 本文包括所有
这些类型的 COM + 组件的调试步骤。
2.1.如何调试本地 COM + 组件
本地 COM + 组件与客户端应用程序安装在的同一台计算机上。 若要调试本地 COM + 组
件,使用下面的过程。
系统必备组件
在尝试调试本地 COM + 组件之前,请确保您具有以下项:
在本地计算机上安装配置 COM + 应用程序
注意 将 DLL 添加到 COM + 应用程序之前,您必须在调试模式下生成 DLL。
COM + 组件的源文件
注意 源代码文件必须与 COM + 组件的运行版本相匹配。
COM + 组件的符号的调试信息
注意 符号调试信息包含在项目的.pdb 文件中。
在客户端应用程序中创建本地的 COM + 组件的实例并调用一个方法或本地的 COM +
组件的属性
客户端应用程序的源文件
客户端应用程序的的调试符号信息
注意 符号调试信息包含在项目的.pdb 文件中。
设置调试的文件
源文件和为 COM + 组件的该符号调试信息 (.pdb 文件) 复制到相同的文件夹。 因此,
该文件将定位设置 Visual Studio 环境时更容易。
设置 Visual Studio 环境
打开客户端应用程序的项目
在解决方案资源管理器中右击该解决方案,然后单击 属性。
展开 通用属性,然后单击 调试源文件。
单击 新行。
单击省略号按钮 (...)。
找到 COM + 组件的源文件位于,然后单击 打开 位置目录。
设置调试符号文件为 COM + 组件。
在 Visual Studio 2005 中,请按照下列步骤操作:
在 解决方案属性页 对话框中单击 确定。
在 工具 菜单上单击 选项。
展开 调试,然后单击 符号。
单击 新行。
键入为 COM + 组件.pdb 文件所在的文件夹的路径,然后单击 确定。
在 Visual Studio.net 中,请按照下列步骤操作:
在 解决方案属性页 对话框中单击 调试符号文件。
单击 新行。
单击省略号按钮 (...)。
找到.pdb 文件为 COM + 组件所在的文件夹,然后单击 打开。
单击 确定。
您可以在客户端应用程序代码中,创建本地的 COM + 组件的实例的位置添加一个断点。
打开 COM + 组件的源文件,在 COM + 组件源代码中添加断点。
调试本地 COM + 组件
启动 Visual Studio 调试器。
当 Visual Studio 调试器到达该断点在客户端应用程序中的时, 单步执行代码。
在创建 COM + 组件的实例时附加到 Dllhost.exe 进程。
在 Visual Studio 2005 中,请按照下列步骤操作:
在 调试 菜单上单击 附加到进程。
单击 Dllhost.exe,然后单击 附加。
在 Visual Studio.net 中,请按照下列步骤操作:
在 调试 菜单上单击 进程。
单击 Dllhost.exe,然后单击 附加。
在 Visual Studio.net 中选择要使用下列方法之一来调试代码的类型:
如果 COM + 组件使用非托管的代码,请单击以选中 本机 复选框,单击 确定,然后
单击 关闭。
如果 COM + 组件使用托管的代码,请单击以选中 公共语言运行库 复选框,单击 确
定,然后单击 关闭。
注意 在 Visual Studio 2005 中,此步骤不是必需的,visual Studio 2005 自动确定要调试
代码的类型。
在 调试 菜单上单击 继续。
请注意,Visual Studio 调试器到达 COM + 组件源代码中设置断点。
在 调试 菜单上单击 继续。
当 Visual Studio 调试器离开 COM + 函数时,您将收到以下消息:
为任何调用堆栈框架不加载任何符号。 不能显示源代码。
单击 确定。
在 调试 菜单上单击 继续。
Visual Studio 调试器到达客户端的应用程序代码的结尾,Visual Studio 调试器停止运
行。
在 调试 菜单上单击 全部分离
2.2.如何调试远程 COM + 组件
从客户端应用程序在另一台计算机上安装远程 COM + 组件。 当您调试远程 COM + 组件
时,您必须运行下列 Visual Studio 调试器的实例:
为客户端应用程序在 Visual Studio 调试器的实例
Visual Studio 调试器为远程 COM + 组件的实例
注意 此实例的 Visual Studio 将附加到远程计算机运行在 Dllhost.exe 过程。
若要调试远程 COM + 组件,使用下面的过程。
系统必备组件
在尝试调试远程 COM + 组件之前,请确保您具有以下项:
在远程计算机上安装一个 COM + 应用程序
COM + 组件 (DLL) 安装 COM + 应用程序中
注意 将 DLL 添加到 COM + 应用程序之前,您必须在调试模式下生成 DLL。
COM + 组件的源文件
注意 源代码文件必须与 COM + 组件的运行版本相匹配。
COM + 组件的符号的调试信息
注意 符号调试信息包含在项目的.pdb 文件中。
正确的远程调试安装程序
注意 这包括正确的安全设置承载 COM + 组件在远程计算机上。 有关如何设置远程调
试的详细信息请访问下面的 Microsoft 开发人员网络 (MSDN) 的网站:
http://msdn2.microsoft.com/en-us/library/y7f5zaaa(vs.71).aspx
客户端应用程序需要创建远程 COM + 组件的实例并调用一个方法或远程的 COM + 组
件的属性
客户端应用程序的源文件
客户端应用程序的的调试符号信息
注意 符号调试信息包含在项目的.pdb 文件中。
设置调试文件
在远程计算机上将为 COM + 组件的该符号调试信息 (.pdb 文件) 复制到同一 DLL 所在
的文件夹中。
为客户端应用程序的实例启动 Visual Studio 调试器
1. 打开客户程序,设置断点,并启动调试。
为远程 COM+组件的实例启动 Visual Studio 调试器
启动 Visual Studio。
附加到 Dllhost.exe 进程。
在 Visual Studio 2005 中,请按照下列步骤操作:
1. 在 工具 菜单上单击 附加到进程。
2. 在 限定符 框中键入 COM + 组件的安装位置在远程计算机的名称。
3. 单击 刷新。
4. 单击 Dllhost.exe,然后单击 附加。
在 Visual Studio.net 中,请按照下列步骤操作:
1. 在 工具 菜单上单击 调试进程。
2. 在 名称 框中键入 COM + 组件的安装位置在远程计算机的名称。
3. 单击 刷新。
4. 单击 Dllhost.exe,然后单击 附加。
Visual Studio.net 中需要选择要使用下列方法之一来调试代码的类型:
如果 COM + 组件使用非托管的代码,请单击以选中 本机 复选框,单击 确定,然后
单击 关闭。
如果 COM + 组件使用托管的代码,请单击以选中 公共语言运行库 复选框,单击 确
定,然后单击 关闭。
注意 在 Visual Studio 2005 中,此步骤不是必需的。 visual Studio 2005 自动确定要调
试代码的类型。
为 COM + 组件设置调试符号文件。
在 Visual Studio 2005 中,请按照下列步骤操作:
在 工具 菜单上单击 选项。
展开 调试,然后单击 符号。
单击 新行。
键入远程 COM + 组件的.pdb 文件所在的文件夹的路径,然后单击 确定。
在 Visual Studio.net 中,请按照下列步骤操作:
在解决方案资源管理器中右击该解决方案,然后单击 属性。
展开 通用属性,然后单击 调试符号文件 选项卡。
单击 新行。
单击省略号按钮 (...)。
键入远程 COM + 组件的.pdb 文件所在的文件夹的路径,然后单击 确定。
在 调试 菜单上单击 全部中断。
在 调试 菜单上指向 Windows,然后单击 模块。
在列表中找到 COM + 组件的 DLL。
在 Visual Studio 2005 年确认 符号状态 列显示 已加载的符号。
在 Visual Studio.net 中确认 信息 列显示 已加载的符号。
注意 如果 符号状态 列或 信息 列显示 加载无符号, 用鼠标右键单击为 COM + 组件
DLL,然后单击 加载符号。 如果符号的路径的提示定位.pdb 文件所在的远程计算机上的文
件夹,然后单击 打开。
在 调试 菜单上单击 继续。
在 文件 菜单上指向 打开,然后单击 文件。
找到 COM + 组件的源文件,然后单击 打开。
在 COM + 组件源代码中希望 Visual Studio 调试器停止添加断点
调试远程 COM + 组件
切换到客户端应用程序的实例的 Visual Studio 调试器
在 调试 菜单上单击 继续。
切换到远程 COM + 组件的实例的 Visual Studio 调试器。
注意,请在 Visual Studio 调试器中 COM + 组件源代码处设置断点。
3.关于 NUnit 单元测试
------在 VS2005 下应用 NUnit 进行关于数据库的单元测试
好多人都在用 NUnit 测试,我也是其中一员,但是我想许多人也遇到过和我一样的困难,会
者不难难者不会, 我想我遇到的这些困难如何解决的给大家分享一下,也能避免大家少走些
弯路。
如果您还不了解 NUnit,您可以点击下载一个关于 NUnit 的中文帮助文档, 我这里要
说明的是,这边文章不是我翻译的,是由 NUnit 专家 Dorian Deng 翻译,只不过我是把它
整理成了 PDF 文档供大家下载。
大家知道,单元测试的优点之一就是可以反复测试, 但是对应数据库的单元测试怎
么办呢。新增一条记录后然后再删除调,Update 一条记录后在 Update 回来,关键是 Update
回来原来的数据是什么,在测试前保存,我想这不是一个好的方案。有人建议为资料库的单
元测试建立一个专门测试的数据库, 我感觉这也不是一个好的方案, 因为新增一条记录你还
必须自己控制删除,否则在新增记录的时候还是会导致测试失败, 因为已经存在了相关记录。
因此这也就是我要引出的主角了“EnterpriseServices”组件。
网上也有不少关于他测试实现单元测试的方法。 我看了一下,其实都是千篇一律来
自同一文档,最关键的是 vs003 的,我在 Vs2005 上进行编译的时候,总是测试不过去,所
以我感觉才有必要写这篇文章了。闲言少叙,还是转入正题吧。
环境: VS2005、
、
NUnit(我用的 2.4.6)
SQL Server(数据库测试当然必不可少,我用的 SQL Server2000)
第一步:首先建立一个新工程 AccountTest(这是我给部门人员做演示的时候用的专案,
大家将就一下) 。
第二步:为此专案添加 EnterpriseServices 组件。当然作单元测试,也应该添加上 NUnit
框架
第三步:编写测试基础类
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4. using System.EnterpriseServices;
5. using NUnit.Framework;
6. using System.Runtime.InteropServices;
7. using System.Data.SqlClient;
8. namespace Bank
9. {
10. ///
11. /// 這是一個測試各種单元测试的基礎類,所有的要操作數據庫的測試類都從它繼承
12. /// 這樣就能保證所有的測試操作無論是新增、修改、刪除都能順利測試成功,但是并吧
產生任何垃圾數據
13. /// 也不會修改任何實際運行的數據庫
14. ///
15. [Transaction(TransactionOption.Required)]
16. [TestFixture]
17. public class DatabaseFixture : ServicedComponent
18. {
19. protected SqlConnection Connect = null;
20. ///
21. /// 构造函数
22. ///
23. public DatabaseFixture()
24. {
25. }
26. ///
27. /// 本属性标识此测试方法结束后调用,所有事务的撤销工作都是由它来实现的
28. ///
29. [TearDown]
30. public void TransactionTearDown()
31. {
32. if (ContextUtil.IsInTransaction)
33. {
34. ContextUtil.SetAbort();
35. }
36. }
37. ///
38. /// 我在这里建立了数据库链接,其实不建立也行
39. ///
40. [SetUp]
41. public void SetFirst()
42. {
43. try
44. {
45. Connect = new SqlConnection(@"Integrated Security=SSPI;
46. Persist Security Info=False;User ID=sa;Initial
Catalog=NorthWind;Data Source=127.0.0.1");
47. Connect.Open();
48. }
49. catch (Exception ex)
50. {
51. }
52. }
53. }
54. }
第四步: 创建签名 Key
调用命令行程序(路径=系统盘:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\)
sn.exe -k mytest.snk
然后把创建好的 mytest.snk 加入的工程中。设置参见下图(选中工程右键--Properties
--Signning)
第五步:创建单元测试文件(代码如下)
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4. using NUnit.Framework;
5. using System.Data;
6. using System.Data.SqlClient;
7. namespace Bank
8. {
9. [TestFixture]
10. public class DataTest : DatabaseFixture
11. {
12. ///
13. /// 这个就是我建立的测试方法,大家发现反复执行,也不会实际把数据新增到数据
库中
14. ///
15. [Test]
16. public void InsertTest()
17. {
18. string SQL = "insert Categories(CategoryName, Description) values('测试
类型', '测试类型描述')";
19. Assert.IsTrue(ExecSQLNoQuery(SQL)); //如果新增失败这行代码测试无法
通过
20. //执行查询,看是否真的新增到数据库
21. DataSet ds = QueryResult("select * from Categories where CategoryName='
测试类型'");
22. //下面两句可以测试是否真的添加到了数据库
23. Assert.IsNotNull(ds);
24. Assert.AreEqual(1, ds.Tables[0].Rows.Count);
25. //测试新增的数据和是否正确
26. StringAssert.IsMatch("测试类型描述FF",
ds.Tables[0].Rows[0]["Description"].ToString());
27. }
28. ///
29. /// 执行没有返回结果的SQL语句
30. ///
31. protected bool ExecSQLNoQuery(string SQL)
32. {
33. SqlCommand Command = new SqlCommand();
34. Command.Connection = Connect;
35. Command.CommandType = CommandType.Text;
36. Command.CommandText = SQL;
37. bool retFlag = false;
38. try
39. {
40. Command.ExecuteNonQuery();
41. retFlag = true;
42. }
43. catch (Exception ex)
44. {
45. }
46. finally
47. {
48. Command.Dispose();
49. }
50. return retFlag;
51. }
52. ///
53. /// 提供SQL语句返回查询结果
54. ///
55. protected DataSet QueryResult(String SQL)
56. {
57. SqlCommand Command = new SqlCommand();
58. Command.Connection = Connect;
59. Command.CommandType = CommandType.Text;
60. Command.CommandText = SQL;
61. SqlDataAdapter Adapter = new SqlDataAdapter(Command);
62. DataSet ds = new DataSet();
63. bool retFlag = false;
64. try
65. {
66. Adapter.Fill(ds);
67. retFlag = true;
68. }
69. catch (Exception ex)
70. {
71. }
72. finally
73. {
74. Adapter.Dispose();
75. Command.Dispose();
76. }
77. return retFlag == true ? ds : null;
78. }
79. }
80. }
第六步:编译成功,进行测试
你可以在选中工程右键--Properties--debug--start external program 选中
UNit 程序 ,
(这种情况可以进入调试状态跟踪运行) 你也可以直接执行 UNit 测试程序加
载刚才编译的 dll 进行测试。
错误出现了如下图,这个错误捆扰了好一天多的时间,主要是由于在 2005 下
以前也没有使用过 EnterpriseServices 的经验.
我一开始怀疑是不是网上的例子是不是本身有问题呢,我是按照例子亦步亦趋的做的,
唯一的区别就是 vs003 和 vs005 的区别,最后我在 Vs2003 下试验了一下,能正常通过。
我想要么是 VS2005 组件有更新,调用模式有区别,或是 2005 环境要有的工程设置要
做变更。 我查了许多资料,并仔细分享错误,其中有这么一个提示《而且符合所有其
他 ComVisibility 需求》 引起了我的注意,因为我发现 Assemblyinfo.cs 文件中有[assembly:
ComVisible(false)]一行代码,于是我修改为[assembly: ComVisible(true)],编译运行测试
结果。哈哈,终于看到我梦寐以求的如下界面了。
本来到这里我本以为大功告成了,结果后面的错误又让我焦头烂额了。
新问题:由于这个是测试的研究例子,我开始实际实施到我正在进行的项目中。结果运
行又出现了新错误。 我调试发现进行数据库链接的时候出现“已停用分散式交易管理員
(MSDTC) 的網路存取。請使用元件服務系統管理工具啟用 DTC,以使用 MSDTC 安全性
設定中的網路存取”后来发现是本机的 MSDTC 服务的问题,我的是 XP 系统。
设置步骤如下。 进入控制面板--系统管理工具--组件服务。 点击我的电脑--属性
--MSDTC--安全性设定,如下图
我一口气钩了许多我认为相关的选项,大家可以看到我上面的设置。
在后来的编译中我还发现的类似一些错误, 我就不一一详细描述了, 不过我把上面没有
提到的错误的解决方案列举如下 :
1: MSDTC on server 'ITEC-KS-CCNET' is unavailable. 为远端 MSDTC 没有启动,在组件服
务中可以启动。
2: Window2003 下,MSDTC 并没有缺省安装,还需要手工安装一下,方法是新增删除
程序中,点击新增组件,然后选中 Application Servers 点击详细资料,然后选中启用网
络 DTC 存取,保存安装即可。当然安装后也要进入组件服务设置一下 MSDTC 的安全性
设定。
3: 还有一个错误是您必須具備系統管理認證才能執行此工作。 請連絡您的系統管理員
以尋求協助。最后我发现是数据库服务器和运行此测试程序的机器根本不在一个网段,
相互 COM+组件无法互相访问,因为我用 Telnet 测试对方 135,139 端口根本不能通讯,
因此需要开启此两个端口打开 DCOM 服务才可以。
我想经过上面的这写设置,你一定会看到你数据库单元测试的绿色通过条
4.配置 SQL Server 2005 以允许远程连接
在尝试从远程计算机连接到 Microsoft SQL Server 2005 实例时, 可能会接收到错误
消息。在使用任何程序连接到 SQL Server 时都可能会发生此问题。例如,在使用
SQLCMD 实用工具连接到 SQL Server 时收到以下错误消息:
Sqlcmd:错误:Microsoft SQL Native Client:建立到服务器的连接时发生错误。连
接到 SQL Server 2005 时,默认设置 SQL Server 不允许远程连接这个事实可能
会导致失败。
如果没有将 SQL Server 2005 配置为接受远程连接,则可能会发生此问题。默认情
况下,SQL Server 2005 Express Edition 和 SQL Server 2005 Developer Edition 不允许远程
连接。若要配置 SQL Server 2005 以允许远程连接,请完成以下所有步骤:
在您要从远程计算机连接到的 SQL Server 实例上启用远程连接。
打开 SQL Server Browser 服务。
配置防火墙以允许与 SQL Server 和 SQL Server Browser 服务相关的网络通讯。
本文介绍如何完成这些步骤中的每一步。
若要在 SQL Server 2005 实例上启用远程连接并打开 SQL Server Browser 服务,请使用
SQL Server 2005 外围应用配置器工具。在安装 SQL Server 2005 时会安装外围应用配置
器工具。
为 SQL Server 2005 Express Edition 或 SQL Server 2005 Developer Edition 启用远程
连接
必须为要从远程计算机连接到的每个 SQL Server 2005 实例启用远程连接。为此,请按
照下列步骤操作:
1. 单击“开始” 、
,依次指向“程序”“Microsoft SQL Server 2005”和“配置工具”,然
后单击“SQL Server 外围应用配置器” 。
2. 在“SQL Server 2005 外围应用配置器”页上,单击“服务和连接的外围应用配置
器”。
3. 在“服务和连接的外围应用配置器”页上,展开“数据库引擎” ,依次单击“远程
连接” ,
和“本地连接和远程连接” 单击适用于您的环境的相应协议, 然后单击“应
用”。
注意:请在接收到以下消息时单击“确定” :
直到重新启动数据库引擎服务后,对连接设置所做的更改才会生效。
4.在“服务和连接的外围应用配置器”页上,展开“数据库引擎” ,依次单击“服务”
和 ,
“停止” 等待 MSSQLSERVER 服务停止,然后单击“启动”以重新启动 MSSQLSERVER
服务。
启用 SQL Server Browser 服务
如果您是通过使用实例名称来运行 SQL Server 2005 并且在连接字符串中没有使用特
定的 TCP/IP 端口号,则必须启用 SQL Server Browser 服务以允许远程连接。例如,使
用 <计算机名>\SQLEXPRESS 的默认实例名称安装的 SQL Server 2005 Express。不管您正
在运行多少个 SQL Server 2005 实例,只需要启用一次 SQL Server Browser 服务。若要
启用 SQL Server Browser 服务,请执行以下步骤。
重要说明: 这些步骤可能会增加您的安全风险。 这些步骤还可能导致您的计算机或网络
更易于受到恶意用户或恶意软件(如病毒)的攻击。我们之所以推荐本文介绍的这一过
程,是为了使程序能够按照设计意图运行,或者为了实现特定的程序功能。我们建议在
进行这些更改之前, 充分考虑在您的特定环境中实施这一过程可能带来的风险。 如果您
选择实施此过程, 请采用任何适当的附加步骤以保护您的系统。 我们建议只有确实需要
这一过程时才使用它。
1. 单击“开始” 、 ,然
,依次指向“程序”“Microsoft SQL Server 2005”和“配置工具”
后单击“SQL Server 外围应用配置器” 。
2. 在“SQL Server 2005 外围应用配置器”页上,单击“服务和连接的外围应用配置
器” 。
3. 在“服务和连接的外围应用配置器”页上,单击“SQL Server Browser” ,在“启动
类型”中单击“自动”选项,然后单击“应用” 。
注意:在单击“自动”选项后,每次启动 Microsoft Windows 时将自动启动 SQL
Server Browser 服务。
4. 单击“启动” ,然后单击“确定” 。
注意:在计算机上运行 SQL Server Browser 服务时,计算机显示其上运行的每个 SQL
Server 实例的实例名称和连接信息。如果不启用 SQL Server Browser 服务并且通过分
配的 TCP 端口直接连接到 SQL Server 实例,则可以降低此风险。本文不讨论如何通过
TCP 端口直接到 SQL Server 实例。有关 SQL Server Browser 服务和连接到 SQL Server
实例的更多信息,请参见《SQL Server 联机丛书》中的以下主题:
SQL Server Browser 服务
连接到 SQL Server 数据库引擎
客户端网络配置
在 Windows 防火墙中创建例外
这些步骤适用于 Windows XP Service Pack 2 (SP2) 和 Windows Server 2003 中包含的
Windows 防火墙版本。如果您使用的是不同的防火墙系统,请参阅相应的防火墙文档
以了解更多信息。
如果在运行 SQL Server 2005 的计算机上运行防火墙,将会阻止访问 SQL Server 2005
的外部连接,除非 SQL Server 2005 和 SQL Server Browser 服务可以通过防火墙进行通
信。 必须为每个要接受远程连接的 SQL Server 2005 实例创建一个例外, 并为 SQL Server
Browser 服务创建一个例外。
在安装 SQL Server 2005 的程序文件时,SQL Server 2005 会使用一个实例 ID 作为路径
的一部分。若要为每个 SQL Server 实例创建一个例外,必须确定正确的实例 ID。若要
获取实例 ID,请执行以下步骤:
1. 单击“开始” 、
,依次指向“程序”“Microsoft SQL Server 2005”和“配置工具”,然
后单击“SQL Server 配置管理器” 。
2. 在“SQL Server 配置管理器”中,单击右窗格中的“SQL Server Browser 服务” ,右
键单击主窗口中的实例名称,然后单击“属性” 。
3. 在“SQL Server Browser 属性”页上,单击“高级”选项卡,定位到属性列表中的
实例 ID,然后单击“确定” 。
,再单击“运行”
若要打开 Windows 防火墙,请单击“开始” ,键入 firewall.cpl,然后
单击“确定”。
在 Windows 防火墙中为 SQL Server 2005 创建例外
1. 若要在 Windows 防火墙中为 SQL Server 2005 创建例外,请执行以下步骤:
2. 在 Windows 防火墙中,单击“例外”选项卡,然后单击“添加程序” 。
3. 在“添加程序”窗口中,单击“浏览” 。
4. 单击 C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe 可执
行程序,单击“打开” ,然后单击“确定” 。
注意:上述路径可能会根据 SQL Server 2005 的安装位置而不同。MSSQL.1 这个占位符
代表的是您在前面过程的步骤 3 中获得的实例 ID。
对于每个需要为其创建例外的 SQL Server 2005 实例,重复步骤 1 到步骤 3。
在 Windows 防火墙中为 SQL Server Browser 服务创建例外
若要在 Windows 防火墙中为 SQL Server Browser 服务创建例外,请执行以下步骤:
在 Windows 防火墙中,单击“例外”选项卡,然后单击“添加程序” 。
在“添加程序”窗口中,单击“浏览” 。
单击 C:\Program Files\Microsoft SQL Server\90\Shared\sqlbrowser.exe 可执行程序,单击
“打开” ,然后单击“确定” 。
注意:上述路径可能会根据 SQL Server 2005 的安装位置而不同。
5.SQL Server 2005 下如何创建修改分区表和
如何查看分区表
来源: http://www.xjcncn.com/school/data/mssql/200707/1413.html
SQL Server 2005 是微软在推出 SQL Server 2000 后时隔五年推出的一个数据库平台, 它的数据
库引擎为关系型数据和结构化数据提供了更安全可靠的存储功能, 使用户可以构建和管理用
于业务的高可用和高性能的数据应用程序。此外 SQL Server 2005 结合了分析、报表、集成
和通知功能。这使企业可以构建和部署经济有效的 BI 解决方案,帮助团队通过记分卡、
Dashboard、Web Services 和移动设备将数据应用推向业务的各个领域。无论是开发人员、数
据库管理员、信息工作者还是决策者,SQL Server 2005 都可以提供出创新的解决方案,并可
从数据中获得更多的益处。
它所带来的新特性,如 T-SQL 的增强、数据分区、服务代理和与.Net Framework 的集成
等,在易管理性、可用性、可伸缩性和安全性等方面都有很大的增强。
SQL Server 2005 表分区的具体实现方法
表分区分为水平分区和垂直分区。水平分区将表分为多个表。 每个表包含的列数相
同,但是行更少。例如,可以将一个包含十亿行的表水平分区成 12 个表,每个小表表示特
定年份内一个月的数据。任何需要特定月份数据的查询只需引用相应月份的表。 而垂直分区
则是将原始表分成多个只包含较少列的表。水平分区是最常用分区方式, 本文以水平分区来
介绍具体实现方法。
水平分区常用的方法是根据时期和使用对数据进行水平分区。例如本文例子,一个
短信发送记录表包含最近一年的数据,但是只定期访问本季度的数据。在这种情况下,可考
虑将数据分成四个区,每个区只包含一个季度的数据。
创建文件组
建立分区表先要创建文件组,而创建多个文件组主要是为了获得好的 I/O 平衡。
一般情况下,文件组数最好与分区数相同,并且这些文件组通常位于不同的磁盘上。每个文
件组可以由一个或多个文件构成,而每个分区必须映射到一个文件组。 一个文件组可以由多
个分区使用。为了更好地管理数据(例如,为了获得更精确的备份控制),对分区表应进行设
计,以便只有相关数据或逻辑分组的数据位于同一个文件组中。使用 ALTER DATABASE,添
加逻辑文件组名:
ALTER DATABASE [DeanDB] ADD FILEGROUP [FG1]
DeanDB 为数据库名称,FG1 文件组名。创建文件组后,再使用 ALTER DATABASE 将
文件添加到该文件组中:
ALTER DATABASE [DeanDB] ADD FILE ( NAME = N'FG1', FILENAME =
N'C:DeanDataFG1.ndf' , SIZE = 3072KB , FILEGROWTH = 1024KB ) TO FILEGROUP [FG1]
类似的建立四个文件和文件组,并把每一个存储数据的文件放在不同的磁盘驱动器
里。
创建分区函数
创建分区表必须先确定分区的功能机制,表进行分区的标准是通过分区函数来决定的。
创建数据分区函数有 RANGE “LEFT | / RIGHT”两种选择。代表每个边界值在局部的哪一边。
例如存在四个分区,则定义三个边界点值,并指定每个值是第一个分区的上边界 (LEFT) 还
是第二个分区的下边界 (RIGHT)[1]。代码如下:
CREATE PARTITION FUNCTION [SendSMSPF](datetime) AS RANGE RIGHT FOR VALUES
('20070401', '20070701', '20071001')
创建分区方案
创建分区函数后,必须将其与分区方案相关联,以便将分区指向至特定的文件组。就是
定义实际存放数据的媒体与各数据块的对应关系。多个数据表可以共用相同的数据分区函
数,一般不共用相同的数据分区方案。可以通过不同的分区方案,使用相同的分区函数,使
不同的数据表有相同的分区条件,但存放在不同的媒介上。创建分区方案的代码如下:
CREATE PARTITION SCHEME [SendSMSPS] AS PARTITION [SendSMSPF] TO ([FG1], [FG2],
[FG3], [FG4])
创建分区表
建立好分区函数和分区方案后, 就可以创建分区表了。 分区表是通过定义分区键值和分
区方案相联系的。插入记录时,SQL SERVER 会根据分区键值的不同,通过分区函数的定义
将数据放到相应的分区。从而把分区函数、分区方案和分区表三者有机的结合起来。创建分
区表的代码如下:
CREATE TABLE SendSMSLog
([ID] [int] IDENTITY(1,1) NOT NULL,
[IDNum] [nvarchar](50) NULL,
[SendContent] [text] NULL
[SendDate] [datetime] NOT NULL,
) ON SendSMSPS(SendDate)
查看分区表信息
系统运行一段时间或者把以前的数据导入分区表后,我们需要查看数据的具体存储情
况,即每个分区存取的记录数,那些记录存取在那个分区等。我们可以通过
$partition.SendSMSPF 来查看,代码如下:
SELECT $partition.SendSMSPF(o.SendDate)
AS [Partition Number]
, min(o.SendDate) AS [Min SendDate]
, max(o.SendDate) AS [Max SendDate]
, count(*) AS [Rows In Partition]
FROM dbo.SendSMSLog AS o
GROUP BY $partition.SendSMSPF(o.SendDate)
ORDER BY [Partition Number]
维护分区
分区的维护主要设计分区的添加、减少、合并和在分区间转换。可以通过 ALTER
PARTITION FUNCTION 的选项 SPLIT,MERGE 和 ALTER TABLE 的选项 SWITCH 来实现。SPLIT 会
多增加一个分区,而 MEGRE 会合并或者减少分区,SWITCH 则是逻辑地在组间转换分区。
性能对比
我们对 2650 万数据,存储空间占用约 4G 的单表进行性能对比,测试环境为 IBM365,
CPU 至强 2.7G*2、内存 16G、硬盘 136G*2,系统平台为 Windows 2003 SP1+SQL Server 2005
SP1。测试结果如表 1:
表 1:分区和未分区性能对比表(单位:毫秒)
测试项目 分区 未分区
1 16546 61466
2 13 33
3 20140 61546
4 17140 61000
说明:
1:根据时间检索某一天记录所耗时间
2:单条记录插入所耗时间
3:根据时间删除某一天记录所耗时间
4:统计每月的记录数所需时间
从表 1 可以看出, 对分区表进行操作比未分区的表要快,这是因为对分区表的操作采用
了 CPU 和 I/O 的并行操作,检索数据的数据量也变小了,定位数据所耗时间变短。
6.SQL Server 性能分析参数
当您怀疑计算机硬件是影响 SQL Server 运行性能的主要原因时,可以通过 SQL Server
Performance Monitor 监视相应硬件的负载, 以便证实您的猜测并找出系统瓶颈。下文将介绍
Memory: Page Faults / sec 如果该值偶尔走高,表明当时有线程竞争内存。如果持续很
Process: Working Set
SQL Server 的该参数应该非常接近分配给 SQL Server 的内存值。在 SQL Server 设定中,
如
果将"set working set size"置为 0, 则 Windows NT 会决定 SQL Server 的工作集的大小。如果
将"set working set size"置为 1,则强制工作集大小为 SQLServer 的分配内存大小。一般情况
Process:%Processor Time
如果该参数值持续超过 95%,表明瓶颈是 CPU。可以考虑增加一个处理器或换一个更快
的
Processor:%Privileged Time
如果该参数值和"Physical Disk"参数值一直很高,表明 I/O 有问题。可考虑更换更快的
硬盘系统。另外设置 Tempdb in RAM,减低"max async IO","max lazy writer IO"等措施都
Processor:%User Time
表示耗费 CPU 的数据库操作,如排序,执行 aggregate functions 等。如果该值很高,可
考虑增加索引,尽量使用简单的表联接,水平分割大表格等方法来降低该值。
Physical Disk:Avg.Disk Queue Length
该值应不超过磁盘数的 1.5~2 倍。要提高性能,可增加磁盘。注意:一个 Raid Disk 实际
SQLServer:Cache Hit Ratio
该值越高越好。如果持续低于 80%,应考虑增加内存。 注意该参数值是从 SQL Server
启动
后,就一直累加记数,所以运行经过一段时间后,该值将不能反映系统当前值。
7.COM+:创建补偿资源管理器以扩展应用程序的事务性功能
摘要
补偿资源管理器 (CRM) 是使用 COM+ 提供的 CRM 工具的任意 COM+ 对象,所谓 CRM
工具是指一组工具, 能够简化为各种业务创建自定义资源管理器的工作, 这些业务需要处理
一些属于事务一部分的非数据库操作(如生成一个文件) 。本文概述了事务处理、分布式事
务和事务的两阶段提交协议,还讨论了 CRM 的实现和配置。
补偿资源管理器 (CRM) 是使用 COM+ 提供的 CRM 工具的任意 COM+ 对象。COM+ 的
CRM 工具是能够简化您自己的资源管理器创建工作的一组工具。您可能会觉得奇怪,为什
么要创建自己的资源管理器呢?在很多业务使用情况中, 用户需要执行一些属于事务一部分
。
的非数据库操作(如生成一个文件)(如果您不确定什么是资源管理器,请参见标题为“事
务处理系统体系结构”的工具条。 )
例如,在我目前开发的应用程序中, 帐单信息通过 XML 发送到第三方记帐系统。 XML 文 将
件发送到第三方记帐系统时,数据库中的表会更新,表明帐单已被输出。在此用例中,执行
了两个操作:将一个 XML 文件写入磁盘;更新数据库,以表明 XML 文件已发送。数据库
更新是一个事务性操作,因为会使用一个资源管理器 (Microsoft SQL Server? 7.0) 来执行此
操作。但是 XML 文件的写入又如何呢?这不是一个事务性操作。Windows 2000 文件系统
不是一个资源管理器。这就产生了几个问题。
假如您先写入 XML 文件,然后再尝试执行数据库更新。如果数据库更新失败,事务会回滚
数据库更新操作, 但是 XML 文件仍保留在您的文件系统中。您可以尝试颠倒一下操作顺序:
先执行数据库更新, 然后再写入文件。 现在,如果数据库更新失败,您就不必再写入 XML 文
件了。如果生成 XML 文件失败,您可以只回滚数据库事务。但是,这样仍可能会存在潜在
问题:如果在执行完这两个操作后、 提交事务之前出现系统崩溃会怎么样?您可以回滚事务,
但是由于生成 XML 文件不是事务的一部分,此文件将仍存在于您的文件系统中。
您真正需要的就是一个支持 COM+ 的资源管理器,以将文件写入磁盘。然后在事务处理过
程中, 您可以通过调用这个文件写入资源管理器上的一个方法, 来指定文件名和文件的内容。
该资源管理器将确保只有在事务提交后, 才将文件实际写入磁盘。 COM+ 的 CRM 工具提供
了您构建类似自定义资源管理器所需的所有工具。 本文附带的代码(可从本文开头的链接下
载)包含了一个 CRM,您可以使用它作为事务的一部分,来编写磁盘文件。这个文件编写
器 CRM 也将用作贯穿全文的运行示例。
事务处理介绍
您必须先了解事务、分布式事务以及两阶段提交协议,才能了解如何构建一个 CRM。事务
是具备以下属性的一系列操作:原子性、一致性、隔离性和持久性。这些属性可组成一个易
于记忆的缩写词 — ACID。通过一个您去银行从储蓄帐户转存 500 美元到支票帐户的示例,
可以帮助您了解这四个属性的含意。 这里实际上发生了两个操作:首先,从储蓄帐户取出 500
美元,然后将这 500 美元存到您的支票帐户里。如果两个操作都成功,您和银行都会满意。
如果两个操作都失败,您们也不必紧张,大家都可以接受这个结果,只要稍后再试一次就行
了。
现在想象一下,如果在帐户转存操作过程中银行的计算机崩溃了,会发生什么情况?首先,
假设银行计算机崩溃正好发生在从储蓄帐户中取出 500 美元后,但在存入支票帐户之前。
这样,您就会损失 500 美元。现在假设另外一种情况:如果在 500 美元记入支票帐户后,
但在从储蓄帐户中取出之前发生银行计算机崩溃,又会发生什么情况呢。您会很高兴,但银
行是不会接受的。这两个操作必需同时成功或同时失败。这就是原子性的定义。
在此例中,一致性的含义是指存入支票帐户的钱数应与从储蓄帐户中取出的钱数相等。借助
于事务处理系统,一致性由应用程序逻辑来强制实施。
隔离性的含义是与帐户转存并发执行的单独事务不应看到无效的中间状态。 在此例中,如果
从储蓄帐户中取出 500 美元,您永远看不到这 500 美元尚未存到支票帐户的情况。隔离性
通常用锁定来实现。
持久性的含义是在事务提交后,由事务进行的更新永远不会丢失。在事务提交之后,系统崩
溃、网络故障,甚至有人不小心拔出了电源线都不应造成更新丢失。大多数数据库(以及其
他类型的事务性资源)通过先将所有需要进行的更改写入持久存储的日记文件中,来确保持
久性。只有在写入日记文件后,才会提交事务。如果在事务提交后系统崩溃或有人从插座中
拔出电源插头,资源管理器会读取其日志并重试所有非永久性的已提交事务,或尝试回滚所
有已中止的事务。
大多数 DBMS 都内置支持开始、提交和回滚等事务处理基元。使用这些事务处理基元的基
本步骤如以下伪代码所示:
try
{
Transaction.Begin
Withdraw $500 from savings account
Deposit $500 into checking account
Transaction.Commit
}
catch (Exception)
{
// if anything goes wrong, roll back the entire operation
Transaction.Rollback
}
开始操作负责启动一个事务。 当事务中的所有操作都完成后, 您可以调用提交函数来提交事
务。如果事务在任何时候失败,您可以调用回滚函数来撤销调用开始函数后的所有操作。
分布式事务
如果事务只涉及单个数据库,事务的处理就简单了。遗憾的是,大多数企业不会把他们所有
的重要信息都存储在单个数据库中。在很多情形下,信息会分布在许多数据库中。例如,一
个网络零售商就可能有一个存储在公司办公室的 Oracle 数据库中的客户帐户信息数据库,
而订单执行信息可能会存储在合作伙伴公司仓库的 SQL Server 数据库中。当一个客户向这
个网络零售商订货时, 该客户的帐户应记入公司办公室的数据库中, 而订单记录应写入合作
伙伴公司的 SQL Server 数据库中。如果总是要求客户支付产品的费用而从不发货,网络零
售商的信誉就会受到损害。因此,为了防止这种情况的发生,更新应作为一个事务在这两个
数据库中同时进行。如果任一个操作失败,每个资源管理器(数据库)都将回滚它的事务部
分。
这种类型的事务被称为分布式事务。分布式事务是一个涉及到多个独立资源管理器的事务:
在此例中是公司办公室的 Oracle 数据库和合作伙伴仓库中的 SQL Server 数据库。每个涉及
到的资源管理器都将提交它们的事务部分,如果有任何资源管理器无法提交它们的事务部
分,则两个资源管理器都将回滚其更新操作。
两阶段提交协议
实现分布式事务的关键就是两阶段提交协议。 在此协议中,一个或多个资源管理器的活动均
由一个称为事务协调器的单独软件组件来控制。此协议中的五个步骤如下:
• 应用程序调用事务协调器中的提交方法。
• 事务协调器将联络事务中涉及的每个资源管理器, 并通知它们准备提交事务(这是第一阶
段的开始) 。
• 为了以肯定的方式响应准备阶段, 资源管理器必须将自己置于以下状态:确保能在被要求
提交事务时提交事务, 或在被要求回滚事务时回滚事务。大多数资源管理器会将包含其计划
更改的日记文件(或等效文件)写入持久存储区中。如果资源管理器无法准备事务,它会以
一个否定响应来回应事务协调器。
• 事务协调器收集来自资源管理器的所有响应。
• 在第二阶段, 事务协调器将事务的结果通知给每个资源管理器。如果任一资源管理器做出
否定响应, 则事务协调器会将一个回滚命令发送给事务中涉及的所有资源管理器。 如果资源
管理器都做出肯定响应, 则事务协调器会指示所有的资源管理器提交事务。一旦通知资源管
理器提交,此后的事务就不能失败了。通过以肯定的方式响应第一阶段,每个资源管理器均
已确保,如果以后通知它提交事务,则事务不会失败。
图 1 和图 2 通过两个顺序图来说明两阶段提交协议。
图 1 事务提交
图 2 事务被回滚
图 1 显示事务成功(提交)。图 2 显示由于某种原因,其中一个资源管理器无法提交时的
两阶段提交协议。
这是一个关于事务、分布式事务和两阶段提交协议的高级介绍。如果您要了解更多信息,请
参阅 Jim Gray 和 Andreas Reuter 合著的 Transaction Processing:Concepts and Techniques 一
书 (Morgan Kaufmann, 1992 年),以及 Philip Bernstein 和 Eric Newcomer 合著的 Principles
of Transaction Processing 一书(Morgan Kaufmann,1997 年) 。
资源管理器的体系结构
现在您已经对事务处理的基本概念有了一个基本了解,我们回过头来再讨论一下 CRM。为
了使用两阶段提交协议,资源管理器必须分两个阶段执行其操作。首先,资源管理器必须将
它控制的资源置于以下状态:确保如果事务提交,可保持更改的持久性;或者,如果事务被
回滚,可撤销更改。其次,资源管理器必须等待事务协调器通知它事务的结果。
大多数资源管理器借助于持久日志文件来实现这种逻辑(请参见标题为“持久日志文件及恢
。在第一阶段,资源管理器将其计划进行的所有工作记录写入持久日志文件,
复”的提要栏)
然后执行所有必要的工作,将其自身处于能够确保事务结果的状态。执行这些步骤之后,资
源管理器就能以肯定的方式响应第一阶段。
第二阶段发生的操作取决于事务的结果。如果事务中涉及的所有资源管理器都表决在第一阶
段后提交,事务协调器将向资源管理器发送一个提交信息,读取日志文件记录,并保持其更
新的持久性。如果参与的任一资源管理器表决要中止事务,事务协调器将向资源管理器发送
一个中止信息,读取日志文件记录,并撤销它在第一阶段所作的任何系统更改
CRM 的体系结构
图 3 显示了一个 CRM 的体系结构。 为了创建一个 CRM, 您必须实现两个 COM 组件: CRM
Worker 和 CRM 补偿器。COM+ 的 CRM 工具提供了 CRM Clerk 和一个持久日志文件。如
果您了解两阶段提交协议以及资源管理器的一般体系结构, 您就会了解这些组件是如何协同
工作的。
图 3 CRM 的体系结构
CRM Worker 是应用程序级别的代码将直接与之交互的唯一组件;它包含了补偿资源管理器
执行的实际业务逻辑。因此,如果您的 CRM 编写 XML 文件,CRM Worker 将相应地包含一
个 WriteToFile 方法。CRM Worker 是一个事务性 COM+ 组件,您应配置该组件以请求一个
事务。当应用程序激活一个 CRM Worker 组件时,CRM Worker 应实例化一个 CRM Clerk 对
象,并使用 CRM Clerk 注册一个补偿器组件,如以下代码所示。
HRESULT hRes;
ICrmLogControl *pCRMLogControl;
hRetval = CoCreateInstance(CLSID_CRMClerk, NULL, CLSCTX_SERVER,
IID_ICrmLogControl,(void **)&pCRMLogControl);
hRetval = pCRMLogControl->RegisterCompensator
(L"Crmserver.MyCompensator",L"MyCompensator",CRMREGFLAG_ALLPHASES);
当 CRM Worker 参与的一个事务提交时,分布式事务协调器 (DTC) 将调用 CRM 补偿器上的
方法。如果事务提交,CRM 补偿器将负责使 CRM Worker 的更改持久化;如果事务中止,将
撤销 CRM Worker 执行的所有工作。
CRM Clerk 实现一个名为 ICrmLogControl 的接口,该接口包含了 CRM Worker 可用于注册
CRM 补偿器以及写入日志文件记录的方法。此接口的方法在图 4 中进行了总结。
CRM 补偿器是一个必须实现 ICrmCompensator 或 ICrmCompensatorVariants 接口的 COM+
组件。ICrmCompensator 专为 CRM 设计,以 C++ 编写。ICrmCompensatorVariants 主要为
CRM 设计,以 Visual Basic 编写,但并不妨碍您在 C++ 的 CRM 中使用这个接口。
ICrmCompensator 接口的方法如图 5 所示(ICompensatorVariants 与之类似)。
ICrmCompensator 接口由 SetLogControl 方法以及两阶段提交协议中每个阶段的三种方法
(准备、 提交和中止) 组成。 在每个阶段的开始, DTC 将调用补偿器上的 SetLogControl 方
法。 SetLogControl 有一个参数:IcrmLogControl 接口指针。在两阶段提交过程中,补偿
器可使用此接口指针将额外的记录写入日志文件。在准备阶段(第一阶段),DTC 将在调用
SetLogControl 之后调用 BeginPrepare 方法。接下来,DTC 将为 CRM Worker 编写的每个
日志记录调用一次 PrepareRecord 方法。
当第一阶段结束时,DTC 将调用 EndPrepare 方法。这个方法可让 CRM 补偿器表决事务的
结果。如果 CRM 补偿器从这个方法返回 false,事务将中止。如果此方法可以确保在以后
需要时提交其事务部分,CRM 补偿器就应从这个方法只返回 true。
然后, DTC 将收集事务中涉及的所有资源管理器的表决。 如果所有资源管理器都表决为提交,
DTC 将在 CRM 补偿器上再次调用 SetLogControl。然后,它将调用 BeginCommit、
CommitRecord(为 CRM Worker 编写的每条记录调用一次)和 EndCommit。如果事务中涉及
的任一资源管理器表决中止事务,则 DTC 将指示 CRM 补偿器中止事务,并调用
SetLogControl、BeginAbort、AbortRecord(为 CRM Worker 编写的每条日志记录调用一次)
和 EndAbort。如果 CRM Worker 或作为 CRM Worker 客户端的一个组件直接中止事务,则
CRM 补偿器将不接受准备阶段的方法调用序列; 它将只接受中止序列。 CRM 补偿器必须能够
处理所有这些情况。
实现 CRM
当 CRM Worker 使用 CRM Clerk 注册其 CRM 补偿器之后,应使用 CRM Clerk 上的
ICrmLogControl 方法来写入日志记录。CRM Worker 应在日志中写入足够的信息,以便 CRM
补偿器可以撤销 CRM Worker 执行的所有操作。在进行实际工作之前,CRM Worker 应始终
先使用 ICrmLogControl 上的 ForceLog 方法写入日志文件记录并将这些记录存储到持久
日志文件中,如图 6 中的代码所示。
此预先写入的策略很重要,因为 CRM Worker 在执行其业务逻辑时可能会失败。如果发生这
种情况,DTC 将向 CRM 补偿器发送方法调用的 SetLogControl、BeginAbort、AbortRecord
(为 CRM Worker 编写的每条日志记录使用一次)和 EndAbort 序列。如果 CRM Worker 没
有先写入其日志文件记录,CRM 补偿器将没有足够的信息来撤销 CRM Worker 在失败前执行
的任何操作。
CRM Worker 和 CRM 补偿器被设计为可以指示您写入持久日志文件的信息和您在 CRM 补偿
器中放入方法的逻辑。对于文件编写器 CRM,有两种设计方案。首先,CRM Worker 可以将
文件写入磁盘。在这种情况下,CRM Worker 只能在日志里写入文件路径,并且如果事务中
止,CRM 补偿器必须通过它的一个中止方法来删除文件。第二种方案是,CRM 补偿器可以只
在事务提交时将文件写入磁盘。在这种情况下,CRM Worker 必须在持久日志中写入文件路
径和文件内容,以便 CRM 补偿器知道写入了什么以及写在何处。您的选择取决于您的需要
和您尝试写入的文件大小(您可能不希望在日志中写入好几兆的文件内容)。这一选择还会
影响事务的隔离性。在下一节里,我将更多地讨论这最后一点。
重要事项
在实现 CRM 补偿器时,您必须记住一些事情。首先,您不能假定处理准备阶段方法调用集
的同一 CRM 补偿器实例也将处理提交阶段的方法调用。如果客户端尝试提交一个事务,但
有人从墙上拔出电源线或在提交阶段出现系统崩溃,则在恢复过程中不会重复调用准备方
且
法, CRM 补偿器将只接收一组中止或提交方法调用。每个阶段必须在 CRM 补偿器中实现,
以便与其他阶段相互独立。这就是为什么在每个阶段的开始调用 SetLogControl 并在每个
阶段都将日志记录传递给 CRM 补偿器的原因。
另外还需要记住的是, CRM 体系结构不会定位隔离。 隔离意味着运行在事务之外的代码在事
务结束之前不应看到事务的结果。通常,隔离使用锁定来实现。文件编写器 CRM 在 CRM
Worker 中将其文件写入磁盘,如果事务中止,将在补偿器中将文件删除。这种实现方式会
出现这样一种可能:如果其他软件在 CRM Worker 执行其工作后、但在 CRM 补偿器有机会
删除文件之前尝试进行读操作,就可以从一个中止的事务中读出文件。
对于文件编写器来说,解决这一问题很简单:文件的内容可以写入持久日志中,这样写入磁
盘文件的操作就可以延迟到事务提交以后。在这种情况下,CRM Worker 将执行一些有效性
检查(例如,验证指定的文件夹是否存在),然后将文件路径和内容写入持久日志。
CRM 补偿器可以成功完成准备阶段,但此时可能会有人从墙上拔去机器的电源插头。客户端
应用程序会以为事务已提交,但是直到第二阶段(提交阶段)结束后,事务才算真正完成。
这种情况可能发生在第一阶段完成后的某个不确定时间。需要指出的是,这个问题并非是
CRM 构建的资源管理器专有,许多其他资源管理器也存在这个问题。幸运的是,这种故障发
生的几率很小,因此在大多数情况下,无需构建处理这种情况的机制。
另外还需要记住的是,您需要确保在提交阶段和中止阶段所作的更新是幂等的。 幂等操作的
含义就是不管此操作执行几次,操作的最终结果都是相同的。例如,给文件追加文本就不是
一个幂等操作。如果您执行该操作几次,文件中的文本将会重复多次。但是如果以截断模式
打开和写入一个文件,其现有内容就会被删除。这是一个幂等操作,因为不管您执行此操作
多少次,文件所包含的内容都是相同的。CRM 补偿器的提交和中止阶段必须是幂等的,因为
这些操作可能会执行多次。请记住,如果在提交或中止阶段发生系统崩溃, 则在恢复过程中,
该阶段将通过相同的日志记录来重复。
配置 CRM
实现 CRM Worker 和补偿器之后, 您的工作并没有完成。 由于 CRM Worker 和补偿器是 COM+
组件,因此必须使用 Windows 2000 组件服务资源管理器来配置它们,以便正确工作。您必
须创建一个新的 COM+ 应用程序, 并将这两个组件添加到其中 (您应在同一个 COM+ 应用程
序中安装 CRM Worker 和 CRM 补偿器)。如图 7 中所示,配置 CRM Worker 和补偿器。
配置完这两个组件后,您还必须在 COM+ 应用程序中启用 CRM。要执行此操作,请在 COM+ 组
件服务资源管理器中执行以下步骤
图 8 访问 COM+ 应用程序的属性
• 右击您的 COM+ 应用程序,如图 8 中所示。
• 从上下文菜单中选择 Properties(将出现应用程序属性对话框,如图 9 中所示)。
• 单击 Advanced 选项卡(请参见图 9)。
• 选中对话框底部的 Enable Compensating Resource Managers 复选框,然后单击 OK。
图 9 启用 CRM
如果在一个 COM+ 应用程序中选择了 Enable Compensating Resource Managers 选项,则
当 COM+ 应用程序首次启动时,将创建一个 CRM 日志文件,此文件将由该 COM+ 应用程序
中配置的所有 CRM 使用。日志文件的名称将在文件首次创建时,由分配给 COM+ 应用程序
的 GUID 合成。您可以在 Windows 系统目录的 DtcLog 目录(驱动器
号:\WINNT\system32\DTCLog)下找到这个日志文件。CRM 日志文件将带有 crmlog 扩展名。
小结
现在总结一下实现 CRM 的过程,您需要实现两个 COM+ 组件:CRM Worker 和 CRM 补偿器。
COM+ 中的 CRM 工具包括 CRM Clerk 和持久日志文件。CRM Worker 是执行 CRM 业务逻辑
的事务性组件。CRM Worker 可实例化一个 CRM Clerk,然后使用它来注册一个 CRM 补偿器
并写入日志文件记录。然后无论要执行的业务逻辑设计为何种类型,CRM Worker 都将执行。
当 CRM Worker 参与的事务提交时,DTC 将调用 CRM 补偿器必须实现的 ICrmCompensator
(或 ICrmCompensatorVariants)接口上的方法。DTC 在两阶段提交协议的每一阶段都会调
用这些方法,通过这些方法调用,CRM 补偿器既可以在事务提交时使 CRM Worker 的更新持
久,又可以在事务中止时撤销 CRM Worker 所作的所有更改。
8.MSSQL 锁定的模式与锁定的管理知识
1.了解锁
锁定是数据库引擎用来同步多个用户同时对同一个数据块的访问的一种机制。
在事务获取数据块当前状态的依赖关系之前,它必须保护自己不受其他事务对同一数据
进行修改的影响。
2.锁粒度和层次结构
RID 用于锁定堆中的单个行的行标识符。
KEY 索引中用于保护可序列化事务中的键范围的行锁。
PAGE 数据库中的 8 KB 页,例如数据页或索引页。
EXTENT 一组连续的八页,例如数据页或索引页。
HOBT 堆或 B 树。保护索引或没有聚集索引的表中数据页堆的锁。
TABLE 包括所有数据和索引的整个表。
FILE 数据库文件。
APPLICATION 应用程序专用的资源。
METADATA 元数据锁。
ALLOCATION_UNIT 分配单元。
DATABASE 整个数据库。
3.锁模式:
共享锁(S):用于只读操作(SELECT),锁定共享的资源。共享锁不会阻止其他用户读,但
是阻止其他的用户写和修改。
排他(独占)锁(X): 用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同
时对同一资源进行多重更新。
更新锁(U): 当更新事务数据需要获取排它锁时, 必须先获取更新琐。引擎为防止死锁的
发生,一次允许允许一个事务可以获得资源的更新锁(U 锁) ,只有获取更新锁的查询才可
创建排它锁。
意向锁: 用于建立锁的层次结构。意向锁的类型有:意向共享 (IS)、意向排他 (IX) 以及
意向排他共享 (SIX)。引擎在创建共享锁和独占锁之前,先使用意向锁来保护共享锁(S 锁)
或排他锁(X 锁)放置在锁层次结构的底层资源上
架构锁(Sch-M):执行表的数据定义语言 (DDL) 操作时使用架构锁。在架构修改锁起作
用的期间,该锁之外的所有操作都将被阻止.
大容量更新锁(BU 锁):当将数据大容量复制到表,且指定了 TABLOCK 提示或者使用
sp_tableoption 设置了 table lock on bulk 表选项时,将使用大容量更新锁。大容量更新锁(BU
锁) 允许多个线程将数据并发地大容量加载到同一表, 同时防止其他不进行大容量加载数据
的进程访问该表。
4.监视与管理锁
使用 SQL Server Profiler 监视 Locks 事件,来捕获有关跟踪中锁事件的信息的锁事件类
别。
使用系统监视器监视 SQL Server Locks 对象,监视数据库引擎实例中的锁级别。
查询 sys.dm_tran_locks 动态管理视图获得有关数据库引擎 实例中锁当前状态的信息。
使用系统存储过程 sp_lock (Transact-SQL) 返回有关数据库引擎实例中的活动锁的信息。
对于 SQL Server 2005,请改用 sys.dm_tran_locks 动态管理视图。
使用系统视图 sys.syslockinfo (Transact-SQL)返回有关数据库引擎 实例中的活动锁的信
息。对于 SQL Server 2005,请改用 sys.dm_tran_locks 动态管理视图。
5.将死锁减至最少
按同一顺序访问对象。
避免事务中的用户交互。
保持事务简短并处于一个批处理中。
使用较低的隔离级别。
使用基于行版本控制的隔离级别。
将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON,使得已提交读事务使用行版
本控制。
使用快照隔离。
使用绑定连接。
9.SQL Server 死锁总结
1.死锁原理
根据操作系统中的定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,但
因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
死锁的四个必要条件:
互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进
程正占用的资源。
对应到 SQL Server 中,当在两个或多个任务中,如果每个任务锁定了其他任务试图锁定的资
源,此时会造成这些任务永久阻塞,从而出现死锁;这些资源可能是:单行(RID,堆中的单
行)、索引中的键(KEY,行锁)、页(PAG,8KB)、区结构(EXT,连续的 8 页)、堆或 B 树(HOBT)、
表(TAB,包括数据和索引)、文件(File,数据库文件)、应用程序专用资源(APP)、元数据
(METADATA)、分配单元(Allocation_Unit)、整个数据库(DB)。一个死锁示例如下图所示:
说明:T1、T2 表示两个任务;R1 和 R2 表示两个资源;由资源指向任务的箭头(如 R1->T1,
R2->T2)表示该资源被改任务所持有;由任务指向资源的箭头(如 T1->S2,T2->S1)表示该任务
正在请求对应目标资源;
其满足上面死锁的四个必要条件:
(1).互斥:资源 S1 和 S2 不能被共享,同一时间只能由一个任务使用;
(2).请求与保持条件:T1 持有 S1 的同时,请求 S2;T2 持有 S2 的同时请求 S1;
(3).非剥夺条件:T1 无法从 T2 上剥夺 S2,T2 也无法从 T1 上剥夺 S1;
(4).循环等待条件:上图中的箭头构成环路,存在循环等待。
2.死锁排查
(1).使用 SQL Server 的系统存储过程 sp_who 和 sp_lock,可以查看当前数据库中的锁情况;
进而根据 objectID(@objID)(SQL Server 2005)/ object_name(@objID)(Sql Server 2000)可以查看
哪个资源被锁,用 dbcc ld(@blk),可以查看最后一条发生给 SQL Server 的 Sql 语句;
CREATE Table #Who(spid int,
ecid int,
status nvarchar(50),
loginname nvarchar(50),
hostname nvarchar(50),
blk int,
dbname nvarchar(50),
cmd nvarchar(50),
request_ID int);
CREATE Table #Lock(spid int,
dpid int,
objid int,
indld int,
[Type] nvarchar(20),
Resource nvarchar(50),
Mode nvarchar(10),
Status nvarchar(10)
);
INSERT INTO #Who
EXEC sp_who active --看哪个引起的阻塞,blk
INSERT INTO #Lock
EXEC sp_lock --看锁住了那个资源 id,objid
DECLARE @DBName nvarchar(20);
SET @DBName='NameOfDataBase'
SELECT #Who.* FROM #Who WHERE dbname=@DBName
SELECT #Lock.* FROM #Lock
JOIN #Who
ON #Who.spid=#Lock.spid
AND dbname=@DBName;
--最后发送到 SQL Server 的语句
DECLARE crsr Cursor FOR
SELECT blk FROM #Who WHERE dbname=@DBName AND blk<>0;
DECLARE @blk int;
open crsr;
FETCH NEXT FROM crsr INTO @blk;
WHILE (@@FETCH_STATUS = 0)
BEGIN;
dbcc inputbuffer(@blk);
FETCH NEXT FROM crsr INTO @blk;
END;
close crsr;
DEALLOCATE crsr;
--锁定的资源
SELECT #Who.spid,hostname,objid,[type],mode,object_name(objid) as objName FROM #Lock
JOIN #Who
ON #Who.spid=#Lock.spid
AND dbname=@DBName
WHERE objid<>0;
DROP Table #Who;
DROP Table #Lock;
(2).使用 SQL Server Profiler 分析死锁:将 Deadlock graph 事件类添加到跟踪。此事件类使用死
锁涉及到的进程和对象的 XML 数据填充跟踪中的 TextData 数据列。 Server 事件探查器 可 SQL
以将 XML 文档提取到死锁 XML (.xdl)文件中, 以后可在 SQL Server Management Studio 中查看
该文件。
3.避免死锁
上面 1 中列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件,
就可以避免死锁发生,一般有以下几种方法(FROM Sql Server 2005 联机丛书):
(1).按同一顺序访问对象。(注:避免出现循环)
(2).避免事务中的用户交互。(注:减少持有资源的时间,较少锁竞争)
(3).保持事务简短并处于一个批处理中。(注:同(2),减少持有资源的时间)
(4).使用较低的隔离级别。(注:使用较低的隔离级别(例如已提交读)比使用较高的隔离级
别(例如可序列化)持有共享锁的时间更短,减少锁竞争)
(5).使用基于行版本控制的隔离级别:2005 中支持快照事务隔离和指定 READ_COMMITTED
隔离级别的事务使用行版本控制,可以将读与写操作之间发生的死锁几率降至最低:
SET ALLOW_SNAPSHOT_ISOLATION ON --事务可以指定 SNAPSHOT 事务隔离级别;
SET READ_COMMITTED_SNAPSHOT ON --指定 READ_COMMITTED 隔离级别的事务将使用行版
本控制而不是锁定。默认情况下(没有开启此选项,没有加 with nolock 提示),SELECT 语句会
对请求的资源加 S 锁(共享锁);而开启了此选项后,SELECT 不会对请求的资源加 S 锁。
注意: 设置 READ_COMMITTED_SNAPSHOT 选项时, 数据库中只允许存在执行 ALTER DATABASE
命令的连接。在 ALTER DATABASE 完成之前,数据库中决不能有其他打开的连接。数据库不
必一定要处于单用户模式中。
(6).使用绑定连接。(注:绑定会话有利于在同一台服务器上的多个会话之间协调操作。绑定
会话允许一个或多个会话共享相同的事务和锁(但每个回话保留其自己的事务隔离级别),并
可以使用同一数据, 而不会有锁冲突。 可以从同一个应用程序内的多个会话中创建绑定会话,
也可以从包含不同会话的多个应用程序中创建绑定会话。在一个会话中开启事务(begin tran)
后,调用 exec sp_getbindtoken @Token out;来取得 Token,然后传入另一个会话并执行 EXEC
sp_bindsession @Token 来进行绑定(最后的示例中演示了绑定连接)。
4.死锁处理方法:
(1).根据 2 中提供的 sql,查看那个 spid 处于 wait 状态,然后用 kill spid 来干掉(即破坏死锁的
第四个必要条件:循环等待);当然这只是一种临时解决方案,我们总不能在遇到死锁就在用
户的生产环境上排查死锁、Kill sp,我们应该考虑如何去避免死锁。
(2).使用 SET LOCK_TIMEOUT timeout_period(单位为毫秒)来设定锁请求超时。默认情况下,数
据库没有超时期限(timeout_period 值为-1,可以用 SELECT @@LOCK_TIMEOUT 来查看该值,
即无限期等待)。当请求锁超过 timeout_period 时,将返回错误。timeout_period 值为 0 时表
示根本不等待,一遇到锁就返回消息。设置锁请求超时,破环了死锁的第二个必要条件(请
求与保持条件)。
服务器: 消息 1222,级别 16,状态 50,行 1
已超过了锁请求超时时段。
(3). SQL Server 内部有一个锁监视器线程执行死锁检查,锁监视器对特定线程启动死锁搜索
时,会标识线程正在等待的资源;然后查找特定资源的所有者,并递归地继续执行对那些线
程的死锁搜索,直到找到一个构成死锁条件的循环。检测到死锁后,数据库引擎 选择运行
回滚开销最小的事务的会话作为死锁牺牲品,返回 1205 错误,回滚死锁牺牲品的事务并释
放该事务持有的所有锁,使其他线程的事务可以请求资源并继续运行。
5.两个死锁示例及解决方法
5.1 SQL 死锁
(1).测试用的基础数据:
CREATE TABLE Lock1(C1 int default(0));
CREATE TABLE Lock2(C1 int default(0));
INSERT INTO Lock1 VALUES(1);
INSERT INTO Lock2 VALUES(1);
(2).开两个查询窗口,分别执行下面两段 sql
--Query 1
Begin Tran
Update Lock1 Set C1=C1+1;
WaitFor Delay '00:01:00';
SELECT * FROM Lock2
Rollback Tran;
--Query 2
Begin Tran
Update Lock2 Set C1=C1+1;
WaitFor Delay '00:01:00';
SELECT * FROM Lock1
Rollback Tran;
上面的 SQL 中有一句 WaitFor Delay '00:01:00',用于等待 1 分钟,以方便查看锁的情况。
(3).查看锁情况
在执行上面的 WaitFor 语句期间,执行第二节中提供的语句来查看锁信息:
Query1 中,持有 Lock1 中第一行(表中只有一行数据)的行排他锁(RID:X),并持有该行所在页
的意向更新锁(PAG:IX)、该表的意向更新锁(TAB:IX);Query2 中,持有 Lock2 中第一行(表中只
有一行数据)的行排他锁(RID:X),并持有该行所在页的意向更新锁(PAG:IX)、该表的意向更新
锁(TAB:IX);
执行完 Waitfor,Query1 查询 Lock2,请求在资源上加 S 锁,但该行已经被 Query2 加上了 X
锁;Query2 查询 Lock1,请求在资源上加 S 锁,但该行已经被 Query1 加上了 X 锁;于是两
个查询持有资源并互不相让,构成死锁。
(4).解决办法
a).SQL Server 自动选择一条 SQL 作死锁牺牲品:运行完上面的两个查询后,我们会发现有一
条 SQL 能正常执行完毕,而另一个 SQL 则报如下错误:
服务器: 消息 1205,级别 13,状态 50,行 1
事务(进程 ID xx)与另一个进程已被死锁在 lock 资源上,且该事务已被选作死锁牺牲
品。请重新运行该事务。
这就是上面第四节中介绍的锁监视器干活了。
b).按同一顺序访问对象:颠倒任意一条 SQL 中的 Update 与 SELECT 语句的顺序。例如修改第
二条 SQL 成如下:
--Query2
Begin Tran
SELECT * FROM Lock1--在 Lock1 上申请 S 锁
WaitFor Delay '00:01:00';
Update Lock2 Set C1=C1+1;--Lock2:RID:X
Rollback Tran;
当然这样修改也是有代价的,这会导致第一条 SQL 执行完毕之前,第二条 SQL 一直处于阻
塞状态。单独执行 Query1 或 Query2 需要约 1 分钟,但如果开始执行 Query1 时,马上同时
执行 Query2,则 Query2 需要 2 分钟才能执行完;这种按顺序请求资源从一定程度上降低了
并发性。
c).SELECT 语句加 With(NoLock)提示:默认情况下 SELECT 语句会对查询到的资源加 S 锁(共享
锁),S 锁与 X 锁(排他锁)不兼容;但加上 With(NoLock)后,SELECT 不对查询到的资源加锁(或
者加 Sch-S 锁,Sch-S 锁可以与任何锁兼容);从而可以是这两条 SQL 可以并发地访问同一资
源。当然,此方法适合解决读与写并发死锁的情况,但加 With(NoLock)可能会导致脏读。
SELECT * FROM Lock2 WITH(NOLock)
SELECT * FROM Lock1 WITH(NOLock)
d).使用较低的隔离级别。SQL Server 2000 支持四种事务处理隔离级别(TIL),分别为:READ
UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE;SQL Server 2005 中增
加了 SNAPSHOT TIL。默认情况下,SQL Server 使用 READ COMMITTED TIL,我们可以在上面的
两条 SQL 前都加上一句 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,来降低 TIL
以避免死锁;事实上,运行在 READ UNCOMMITTED TIL 的事务,其中的 SELECT 语句不对结
果资源加锁或加 Sch-S 锁,而不会加 S 锁;但还有一点需要注意的是:READ UNCOMMITTED
TIL 允许脏读,虽然加上了降低 TIL 的语句后,上面两条 SQL 在执行过程中不会报错,但执
行结果是一个返回 1,一个返回 2,即读到了脏数据,也许这并不是我们所期望的。
e).在 SQL 前加 SET LOCK_TIMEOUT timeout_period,当请求锁超过设定的 timeout_period 时间
后,就会终止当前 SQL 的执行,牺牲自己,成全别人。
f).使用基于行版本控制的隔离级别(SQL Server 2005 支持):开启下面的选项后,SELECT 不会
对请求的资源加 S 锁,不加锁或者加 Sch-S 锁,从而将读与写操作之间发生的死锁几率降至
最低;而且不会发生脏读。啊
SET ALLOW_SNAPSHOT_ISOLATION ON
SET READ_COMMITTED_SNAPSHOT ON
g).使用绑定连接(使用方法见下一个示例。)
5.2 程序死锁(SQL 阻塞)
看一个例子:一个典型的数据库操作事务死锁分析,按照我自己的理解,我觉得这应该算是
C#程序中出现死锁, 而不是数据库中的死锁; 下面的代码模拟了该文中对数据库的操作过程:
//略去的无关的 code
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
SqlTransaction tran = conn.BeginTransaction();
string sql1 = "Update Lock1 SET C1=C1+1"
10.在 SQL Server 2005 中编写 sp_lock 系统存储过程
做为系统存储过程,sp_lock 可以用来了解服务器的运行情况,通过查看系统的锁定信息诊
断 SQL Server 可能出现的问题。不过系统存储过程 sp_lock 本身存在一些缺陷。对于数据库
管理新手来说, 其返回的结果不够直白, 花费了大量的工作来显示系统中哪个会话造成了最
多锁定, 却并没有提供多少关于这些对象或会话的相应详细信息。 虽然我们可以创建自定义
的脚本来查看这些信息, 但是返回的结果往往过于复杂, 而充其量能返回一些质量低下的信
息。也有其他的一些系统表可以用来查看锁定信息,例如 syslockinfo,但信息的细节同样不
够明了。此外,sp_lock 和 syslockinfo 还有一个更大的问题,那就是他们都是“不建议使用
的特性” ,所以将来的 SQL Server 版本中可能不再包含这些特性。SQL Server 2005 提供的新
的动态管理视图包含了大量锁定细节, 并使我们能够将锁定信息关联起来, 看起来可以更一
目了然。
sys.dm_tran_locks
新的动态视图 sys.dm_tran_locks 能够返回系统中当前活动的锁管理器资源信息。这个视
图返回的信息类型和 sp_lock 一样,但提供了更多细节。关键是这是一个视图,允许数据库
管理员轻松的将其连接到其他表。
自定义 sp_lock 例子
USE MASTER
GO
CREATE PROCEDURE [dbo].[sp_Lock_Detail]
AS
BEGIN
SELECT
SessionID = s.Session_id,
resource_type,
DatabaseName = DB_NAME(resource_database_id),
request_mode,
request_type,
login_time,
host_name,
program_name,
client_interface_name,
login_name,
nt_domain,
nt_user_name,
s.status,
last_request_start_time,
last_request_end_time,
s.logical_reads,
s.reads,
request_status,
request_owner_type,
objectid,
dbid,
a.number,
a.encrypted ,
a.blocking_session_id,
a.text
FROM
sys.dm_tran_locks l
JOIN sys.dm_exec_sessions s ON l.request_session_id = s.session_id
LEFT JOIN
(
SELECT *
FROM sys.dm_exec_requests r
CROSS APPLY
sys.dm_exec_sql_text(sql_handle)
) a ON s.session_id = a.session_id
WHERE
s.session_id > 50
END
首 先 来 看 看 在 这 个 程 序 中 使 用 的 JOIN 语 句 。 该 语 句 将 sys.dm_tran_locks 表 和
sys.dm_exec_sessions 连接起来,以检索关于当前服务器会话的锁定信息。JOIN 语句使我们
能够将会话的详细信息和与该会话相应的锁的详细信息进行关联。
而后,程序在子查询中使用了 LEFT JOIN 语句,用来检索与目前正在执行的语句执行过
程相关的信息。使用 LEFT JOIN 子句是因为服务器中很可能有会话持有目前没有执行的某种
特定类型的锁。如果有查询执行数据当然是最好的了,如果没有,那也不用担心,因为有
LEFT JOIN。
注意在子查询中使用了带有 CROSS APPLY 操作符的 sys.dm_exec_sql_text 函数。这样我
们就可以使用存储在 sys.dm_exec_requests 视图中的 sql_handle 字段来确定正在执行的语句。
sql_handle 包含当前正在执行的 SQL 语句的哈希值,如果你想要解决出现的问题,这就是最
有用的信息之一了。
需要注意的是,强烈建议不要使用“*”从一个查询中返回所有的行(尤其是在一个生产
数据库中),这里只是为了举例而使用的。
该查询剩下的部分用来返回从视图和在连接中使用子查询所获得的详细信息。在
WHERE 语句中,过滤掉任何小于等于 50 的数据库会话,以消除任何系统会话的影响。在这
个系统过程中, 只需要关注用户会话。可以以视图形式实施以上的过程而不需要用存储过程
标记一个系统存储过程
我们可以把自己创建的存储过程标记为系统存储过程,这样就可以在任何数据库环境中
运行该存储过程,并检索该数据库的特定信息。主数据库里创建一个对象,算完成了将一个
对象标记为系统存储过程的第一步。一旦在主数据库中有了该存储过程,接下来就要运行另
外一个系统存储过程来标记该对象。调用另外一个系统存储过程来将自定义的存储过程标记
为系统存储过程如下:
USE MASTER
EXECUTE sp_ms_marksystemobject 'sp_Lock_Detail'
这样我们就可以在自己的 SQL Server 实例下的任何数据库环境中执行 sp_Lock_Detail 存
储过程,并返回该数据库的锁定信息。 这比在每个用户数据库里都创建一个相同的存储过程
容易多了。
接下来我们就来看看如何使用这个新的系统存储过程。首先,创建一个表并向表中载入
一些数据。
CREATE TABLE LockMyData
(
IDCol INT IDENTITY(1,1) PRIMARY KEY, NumberField VARCHAR(5) ) GO DECLARE @I INT SET
@I = 5000 WHILE @i > 0 BEGIN
INSERT INTO LockMyData( NumberField) SELECT datepart(ms, getdate()) + @I SET @I = @I -
1 END
接下来更新表中某事务的记录并使该事务保持开放状态。 在更新过程中,该事务将持有
对这些记录的锁定。然后添加 WITH(HOLDLOCK)锁提示,确保该数据库的事务隔离水平不会
影响到我们的测试。
BEGIN TRANSACTION UPDATE TOP(2000) LockMyData WITH(HOLDLOCK) SET NumberField =
NumberField + IDCol
另起一个独立的查询会话,执行我们之前创建的 sp_Lock_Detail 存储过程。我们将在与
之前发布的 UPDATE 语句相同的数据库环境下执行该存储过程。
EXECUTE sp_Lock_Detail
我们的返回结果超过了 2000 行,表明发布的 UPDATE 指令使用了行级锁(KEY)来发布该
指令。
该存储过程生成了很多有用的数据。我们可以立刻就能看到谁发布了这些指令、 哪些程
序被用来发布这些指令、锁的类型等等信息。如果我们在原始 UPDATE 指令运行的时候运行
这条指令,那么我们就能够看到引起锁定的确切指令了。
总结
系统存储过程是很有用的工具, 使我们能够在一个数据库中编写存储过程, 然后在其他
数据库环境下运行该过程。本文的 sp_Lock_Detail 只是一个例子,告诉我们如何编写自定义
的系统存储过程来监控自己的数据库。 系统存储过程的用途还有很多, 包括获取存储在数据
库中所有表的大小分配,同时生成用来存储 SQL 语句以节省编写代码的时间等等。有兴趣
的读者可以自己尝试,相信您一定会有所收获的。
11.SQL Server 的 System.Transactions 集成 (ADO.NET)
.NET Framework 2.0 版引入了一个可通过 System.Transactions 命名空间访问的事务框架。此
框架公开事务的方式是完全集成在 .NET Framework,包括 ADO.NET。
除了对编程能力的增强之外,System.Transactions 与 ADO.NET 可一起使用,在处理事务时
协调优化。可提升事务是可以根据需要自动提升为完全分布式事务的轻型(本地)事务。
从 ADO.NET 2.0 开始,当您使用 SQL Server 2005 时 System.Data.SqlClient 会提供对可提升
事务的支持。可提升的事务不会调用分布式事务增加的系统开销,除非需要增加的系统开销。
可提升事务是自动的,不需要开发人员参与。
只有一起使用 SQL Server 的 .NET Framework 数据提供程序 (SqlClient) 和 SQL Server 2005
时,才可以使用可提升事务。
创建可提升事务
SQL Server 的 .NET Framework 提供程序支持可提升事务,这种事务通过 .NET Framework
System.Transactions 命名空间中的类处理。可提升事务通过将分布式事务推迟到需要时再创
建,对分布式事务进行优化。如果只需要一个资源管理器,则不会发生任何分布式事务。
说明:
在部分信任方案中,将事务提升为分布式事务时,需要 DistributedTransactionPermission。
有关更多信息,请参见事务管理升级。
可提升事务方案
分布式事务由 Microsoft 分布式事务处理协调器 (MS DTC) 管理,该协调程序集成了事务中
访问的所有资源管理器, 通常会占用大量的系统资源。 可提升事务是 System.Transactions 事
务的一种特殊形式, 有效地将工作委托给简单的 SQL Server 2005 事务。 System.Transactions、
System.Data.SqlClient 和 SQL Server 2005 协调在处理事务时涉及到的工作, 根据需要将其提
升为完全分布式事务。
使用可提升事务的优点是在使用活动 TransactionScope 事务打开某个连接但不打开任何其
他连接时,事务作为轻型事务提交,而不引发完全分布式事务的其他系统开销。
连接字符串关键字
ConnectionString 属性支持关键字 Enlist, 该关键字指示 System.Data.SqlClient 是否将检测事
务上下文并自动在分布式事务中登记连接。如果 Enlist=true,连接将自动在打开的线程的当
前事务上下文中登记。 如果 Enlist=false,SqlClient 连接不会与分布式事务进行交互。 Enlist 的
默认值为 true。如果连接字符串中未指定 Enlist,而在连接打开时检测到一个连接,连接将
自动在分布式事务中登记。
SqlConnection 连 接 字 符 串 中 的 Transaction Binding 关 键 字 控 制 连 接 与 已 登 记 的
System.Transactions 事 务 的 关 联 。 还 可 以 通 过 SqlConnectionStringBuilder 的
TransactionBinding 属性使用。
下表说明可用的值。
关键字 说明
隐式取消绑定 默认值。事务结束时,连接与事务分离,切
换回自动提交模式。
显式取消绑定 事务关闭之前,连接保持附加到事务。如果
关联的事务未处于活动状态或不匹配
Current,则连接将失败。
有关更多信息,请参见使用事务范围实现隐式事务。
使用 TransactionScope
TransactionScope 类 通 过 隐 式 在 分 布 式 事 务 中 登 记 连 接 , 使 代 码 块 事 务 化 。 必 须 在
TransactionScope 块的结尾调用 Complete 方法,然后再离开该代码块。离开代码块将调用
Dispose 方法。如果引发的异常造成代码离开范围,将认为事务已中止。
我们建议您使用 using 块,以确保在退出 using 代码块时,在 TransactionScope 对象上调
用 Dispose 。 如 果 无 法 提 交 或 回 滚 挂 起 的 事 务 , 可 能 会 对 性 能 造 成 严 重 影 响 , 因 为
TransactionScope 的默认超时为一分钟。 如果不使用 using 语句, 必须在 Try 代码块中执行
所有工作,并在 Finally 代码块中显式调用 Dispose 方法。
如果在 TransactionScope 中发生异常,事务将标记为不一致并被弃用。在 TransactionScope
断开后,事务将回滚。如果未发生任何异常,参与的事务将提交。
说明:
默认情况下,TransactionScope 类将创建一个 IsolationLevel 为 Serializable 的事务。根据应
用程序的不同,可能需要考虑降低隔离级别,以避免应用程序中出现大量的争用。
说明:
我们建议您只在分布式事务中执行更新、 插入和删除,因为这些操作会占用大量的数据库资
源。选择语句可能会对数据库资源进行不必要的锁定,在某些方案中,可能需要使用事务进
行选择。 任何非数据库工作应在事务范围之外完成, 除非工作涉及其他事务化的资源管理器。
尽管事务范围内的异常会使事务无法提交,但是,TransactionScope 类没有规定回滚您的代
码在事务本身范围之外所作的任何更改。 如果在事务回滚时需要采取某项措施,必须自己编
写 IEnlistmentNotification 接口的实现并显式在事务中登记。
示例:
使用 System.Transactions 要求具有 System.Transactions.dll 的引用。
下面的函数演示如何针对包装在 TransactionScope 块中的两个不同 SQL Server 实例(由两
个 不 同 的 SqlConnection 对 象 表 示 ) 创 建 可 提 升 事 务 。 该 代 码 使 用 using 语 句 创 建
TransactionScope 代码块并打开第一个连接,该连接自动在 TransactionScope 中登记。事务
最初作为轻型事务登记, 而不是作为完全分布式事务。 仅当第一个连接中的命令没有引发异
常时,才会在 TransactionScope 中登记第二个连接。打开第二个连接后,事务将自动提升
为完全分布式事务。将会调用 Complete 方法,仅当未引发异常时,该方法才会提交事务。
如果在 TransactionScope 代码块中的任意位置引发了异常,将不会调用 Complete,当在
TransactionScope 的 using 代码块结尾处执行 dispose 后,将回滚分布式事务。
// This function takes arguments for the 2 connection strings and commands in order
// to create a transaction involving two SQL Servers. It returns a value > 0 if the
// transaction committed, 0 if the transaction rolled back. To test this code, you can
// connect to two different databases on the same server by altering the connection string,
// or to another RDBMS such as Oracle by altering the code in the connection2 code block.
static public int CreateTransactionScope(
string connectString1, string connectString2,
string commandText1, string commandText2)
{
// Initialize the return value to zero and create a StringWriter to display results.
int returnValue = 0;
System.IO.StringWriter writer = new System.IO.StringWriter();
// Create the TransactionScope in which to execute the commands, guaranteeing
// that both commands will commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
try
{
// Opening the connection automatically enlists it in the
// TransactionScope as a lightweight transaction.
connection1.Open();
// Create the SqlCommand object and execute the first command.
SqlCommand command1 = new SqlCommand(commandText1, connection1);
returnValue = command1.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
// if you get here, this means that command1 succeeded. By nesting
// the using block for connection2 inside that of connection1, you
// conserve server and network resources by opening connection2
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
try
{
// The transaction is promoted to a full distributed
// transaction when connection2 is opened.
connection2.Open();
// Execute the second command in the second database.
returnValue = 0;
SqlCommand command2 = new SqlCommand(commandText2, connection2);
returnValue = command2.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command2: {0}", returnValu
e);
}
catch (Exception ex)
{
// Display information that command2 failed.
writer.WriteLine("returnValue for command2: {0}", returnValue);
writer.WriteLine("Exception Message2: {0}", ex.Message);
}
}
catch (Exception ex)
{
// Display information that command1 failed.
writer.WriteLine("returnValue for command1: {0}", returnValue);
writer.WriteLine("Exception Message1: {0}", ex.Message);
}
}
// If an exception has been thrown, Complete will not
// be called and the transaction is rolled back.
scope.Complete();
}
// The returnValue is greater than 0 if the transaction committed.
if (returnValue > 0)
{
writer.WriteLine("Transaction was committed.");
}
else
{
// You could write additional business logic here, notify the caller by
// throwing a TransactionAbortedException, or log the failure.
writer.WriteLine("Transaction rolled back.");
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}
12.从 WEB SERVICE 上返回大数据量的 DATASET
前段时间在做一个项目的时候,遇到了要通过 WEB SERVICE 从服务器上返回数据量比较大的
DATASET,当然,除了显示在页面上以外,有可能还要用这些数据在客户端进行其它操作。查
遍了网站的文章,问了一些朋友,也找了一些解决方法.
众所周知,如果不用其它方法,直接从 WEB SERVICE 上传回一个 10W 条记录的
DATASET,可想而知的后果是什么,CPU 要占用 100%,且要等上几分钟,这是任何一个项目都无
法忍受的.在我上网找资料的过程中,试验了几种不同的方法,如通过压缩 SOAP 改善 XML Web
service 性能,这篇文章所介绍的方法用了 SOAP 扩展,是通过在 WEB SERVICE 端用已经过时了
的 NZIPLIB 库来压缩 SOAP 响应,据称文本压缩率可达 80%.文章里面的代码是 VB.NET 的,费了
好大劲翻译成 C#的,照上面建项目,但是很可惜,我没有编译成功,总是出错.
这里我找我建好的项目提供大家下载,大家有时间看看是什么问题. SOAP 压缩代码下载
而后,找到了用序列化的方式来减少网络传输量,Microsoft .NET Framework 1.x 中内
建两种将物件序列化的 Formatter 类别,SoapFormatter 和 BinaryFormatter,两种方式均能减
轻网络传输量提高性能,但 SoapFormatter 方式传输的方式其实还是 XML 形式, 加了很多 XML
标识,因此压缩率不是很理想,BinaryFormatter 用纯二进制的方式序列化 DATASET,能使压
缩率大大提高, 这是台湾作者李匡正 (台灣微軟應用架構技術經理提供的例子里对 SQL 范例
库 Northwind 的测试结果:
SoapFormatter BinaryFormatter
Dataset 序列化後 Bytes 數 1,953,078 1,448,399
很显示然 BinaryFormatter 明显优于 SoapFormatter , 而我也确实用了 BinaryFormatter 这
种方式实现了提高效率.
再 者 , 用 微 软 提 供 的 DataSetSurrogate 类 可 以 此 基 础 上 进 一 步 压 缩 数 据 大 小 ,
DataSetSurrogate 在.net 2.0 里自带。这是比较结果.
SoapFormatter BinaryFormatter
Dataset 序列化後 Bytes 數 1,953,078 1,448,399
DataSetSurrogate 序列化後 Bytes 數 2,371,942 575,684
在这里,有两种方式:可把序列化后的数据用文件形式保存在客户端硬盘;也可用 Byte[]方
式传回客户端,以下是代码。
web service 端(文件形式)
[WebMethod(Description="循环获取远程 DATASET")]
public void SurrogateReadTable(string TableName)
{
//把 DataSet 通过 Surrogate Class 序列化成 Binary Stream
DataSet ds;
ds=SqlHelper.ExecuteDataset(cnn,CommandType.Text,"select * from "+TableName);
//实例化 DataSetSurrogate,传取出的 DATASET 到构造函数里
sds = new DataSetSurrogate(ds);
//实例化二进制流
BinaryFormatter bf=new BinaryFormatter();
StreamWriter swDat;
//写到本地一个文件里
swDat = new StreamWriter(@"c:\output_surrogate_dataset.dat");
bf.Serialize(swDat.BaseStream, sds);
//这里可以知道序列化后的文件的大小
long size = swDat.BaseStream.Length;
swDat.Close();
}
客户端
private void button1_Click(object sender, System.EventArgs e)
{
label1.Text=DateTime.Now.ToString();
button1.Enabled=false;
//反序列化 Binary Stream 能通过 Surrogate Class 转换成 DataSet
//从 WEB SERVICE 上读取方法
svs.SurrogateRead("t_busdocbase");
BinaryFormatter bf=new BinaryFormatter();
StreamReader swDat;
swDat = new StreamReader(@"c:\output_surrogate_dataset.dat");
object o=bf.Deserialize(swDat.BaseStream);
DataSet ds;
sds = (DataSetSurrogate) o;
ds = sds.ConvertToDataSet();
dataGrid1.DataSource=ds.Tables[0];
swDat.Close();
}
web service 端(Byte[]方式)
[WebMethod(Description="获取业务资料远程 DATASET")]
public byte[] SurrogateRead1()
{
DataSet ds;
ds=SqlHelper.ExecuteDataset(cnn,CommandType.Text,"select * from t_busdocbase");
sds = new DataSetSurrogate(ds);
MemoryStream s= new MemoryStream();
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(s,sds);
byte[] e = s.ToArray();
return e;
}
客户端
private void button3_Click(object sender, System.EventArgs e)
{
label1.Text=DateTime.Now.ToString();
button3.Enabled=false;
//*反序列化 Binary Stream 能通过 Surrogate Class 转换成 DataSet*/
//从 WEB SERVICE 上读取方法
byte [] bb=svs.SurrogateRead1();
MemoryStream br=new MemoryStream(bb);
BinaryFormatter bf=new BinaryFormatter();
object o=bf.Deserialize(br);
sds = (DataSetSurrogate) o;
ds = sds.ConvertToDataSet();
dataGrid1.DataSource=ds.Tables[0];
br.Close();
}
我个人觉得用 byte[]方式会安全些,毕竟不用在客户端产生文件,不用担心数据的安全。
当然作为从网络上读取数据来说,10W 条是一个不小量,所有的方式包括压缩,序列化等
都是权宜之计,而不是长久之计,在使用当中,我用以上的方法虽然能使网络传输量降低,
且可在很短时间内就把数据显示在 DATAGRID 上,但 CPU 的开销却达到了 100%,这是我一
直头疼的。我后来又用了分页的方式,把 10W 条数据在服务器端就分批取出,每次 500 条,
这样读取时间延长了,但 CPU 开销却未减轻很多,再后来,又用多线程的方式处理,不甚
理想。因此最好的方法就是尽可能的不查询 10W 条数据,通过条件判断等方式减少所需处
理的数据量。
本文从以下文章里借鉴:
http://www.dotnetjunkies.com/PrintContent.aspx?type=tutorial&id=46630AE2-1C79-4D5F-827E-
6C2857FF1D23
http://blog.joycode.com/5drush/archive/2004/05/28/22990.aspx
http://www.chinacs.net/archives/11/2004/08/10/2155.html
http://www.microsoft.com/taiwan/msdn/columns/adonet/AdoNet_20041231.htm
http://www.microsoft.com/china/msdn/library/langtool/vcsharp/miszipcompression.mspx
Related docs
Other docs by bhzz42
??????????? ?????? ?? ??????????? ??????????? ?????????????? ????????? ?? ??????????
Views: 1 | Downloads: 0
Get documents about "