作者: Kimberly L. Tripp, SQLskills.com
摘要:在很多现在的系统中,一般通过数据仓库或分散系统等方式将重要的读取活动与写入活动隔离开来。这样做有很多好处:读取密集型应用程序倾向于需要更多的索引结构、数据冗余甚至是备用的数据视图。事务处理系统需要吞吐量;只要最小的开销就可以产生最好的写入吞吐量。一般说来,读取器和写入器的访问模式是不同的;读取器更倾向于使用较大型的分析性查询,而写入器更倾向于进行单一的插入、更新和删除操作。当这些活动被隔离开来时,管理员就可以将精力集中于更加小型、更加可管理的事务处理系统的恢复策略;OLTP 数据库只是他们的数据冗余“决策支持/分析数据库”工作的一小部分。虽然这么说,但是有时也不总是能够区分得这么清楚。一旦数据被复制/转换/存档到面向分析的数据库后,就必须定期对其进行维护和/或重建。当然,用户可以从查找事务一致版的数据库中得到好处;但是,这个版本已经不再是当前数据,它可能经过好几个小时的构建和索引,并不是用户真正需要的。输入快照隔离。
本文重点讨论什么时候适合使用这种级别的快照隔离、可能存在什么样的平衡以及最佳用法是什么。在阅读本文之前,您应当考虑阅读“SQL Server 联机丛书”中题为“并发问题”的文章。
本页内容
数据访问模式和用法 | |
定义、术语和语法 | |
开发最佳做法 | |
管理最佳做法 | |
更多相关信息 |
数据访问模式和用法
生产数据库的大小增加得非常快,每次业务要求和法规更改,数据保留期限都会延长。此外,随着驱动器容量每 12-18 个月就翻一番以及存储成本的下降,需要“联机”保存的数据量也不断增加。一个解决办法就是将分析与事务处理隔离开来,但尽管这样做对于复杂的深入分析和商业智能探测来说具有很多好处,它在磁盘空间和可管理性方面却并不总是有效。由于存在着更多联机数据以及执行更多活动查询的需要,也就出现了更多当前和实时分析数据争用的需要。
在 SQL Server 2000 中,可以在“读取已提交”事务隔离下最小化争用,因为 Select 语句处理会在资源被读取之后释放读取锁定。默认环境符合标准的 SQL-92 定义,因为只有已提交的数据被读取,未提交的更改是不可见的。但是,尽管只有已提交的数据才能读取,这个标准并不能保证读取一致性——即使是在事务的有效期内。在处理某个数据行之后,资源锁定(一种共享锁定)会立即被释放,这样马上就可以修改该数据行(甚至在读取操作仍在处理其他行时)。
[注意:如果数据移动不像在一个语句中重新读取一行的机会那么重要(比如说,通过彻底和适当的索引创建和维护来减少分割),那么就很难产生这种异常。]
在很多情况下,这是充足和很好的执行机会。只有已提交的更改可见,它们才会很快在最小资源锁定下可见。例如,如果查找当前的总销售额——作为当前处理系统的估计值——那么只能得到估计值,因为在得到这个值之后,它将会很快变得“陈旧”(由于继续处理事务)。事实上,在很多环境中使用了一种限制程度更低的锁定模式,这种模式称为 READUNCOMMITTED(通常也称为 WITH (NOLOCK) 提示,这是它的同义词)。这种环境允许读取未提交的数据;但是,既然销售和/或总体销售数据只是一个估计值,那么了解“进行中”的数据或许就是可以接受的。如果这也不能接受,那么就必须更改隔离级别——这是编程人员所做的更改,目的是为了确保数据重复读取的一致性。
那么,应该如何控制这个度呢?在系统处于活动处理时是否能够返回语句级或事务级读取一致性数据?可以在生产环境中编写一个长期运行查询,请求一致性而不阻碍编写器吗?在 SQL Server 2005 中,向用户提供这种功能的能力可以通过可选的数据库级设置实现,这种数据库级设置可自动更改 READ COMMMITTED 的行为;这提供了非阻碍、非锁定和语句级的读取一致性。
对于事务级一致性,一种新的隔离级别 SNAPSHOT 已经被添加进来;更改这种隔离级别将使事务级一致性成为可以控制的设置。如果不设置任何选项,SQL Server 2005 数据库的默认行为将会和以前版本一样,在事务处理吞吐量和性能是最高目标的很多系统中,将继续使用这种默认行为。如果您希望提供某种形式的非锁定快照(语句级或事务级),我们将使用行版本控制来跟踪行修改。为了进行这种跟踪,在进行更新时数据编写器将会付出一些代价,即使当时没有读取器也是一样。在进行修改之前开始执行的所有事务完成之前,将需要对行版本进行维护,但在所有事务完成后,该版本将会被丢弃。可是,仍然需要创建版本。尽管这种创建版本的代价很低,但是在没有审慎的考虑和具备很多最佳做法之前,不应选择实施这种做法。
使用情境
本节介绍“SQL Server 2005 快照隔离级别”以及新形式的“读取已提交隔离级别”可以如何帮助改进性能、降低延迟以及提高组织中开发人员和数据库管理员的效率。
我们将探讨下列常见的业务情境:
• |
联机事务处理中的应用程序 |
• |
针对实时数据的特殊报告 |
• |
针对副本托管数据库的特殊报告 |
• |
针对实时数据的隔夜报告 |
• |
迁移到常见的数据库技术 |
联机事务处理中的应用程序
第一眼看来,“快照技术”技术的主要用途似乎是读取密集型工作负载,例如数据仓库和运作报告系统,在这些环境中,大型表要求数据库的事务级一致性视图,针对这种大型表的复杂、长期运行查询(尤其是聚合)的表级读取锁定会有效锁定需要更新数据的事务。这并不是针对大型联合和聚合的唯一一种这种技术的应用程序——新的“读取已提交”隔离级别默认行为在语句级上处理快照,它可以显著改进混合工作负载系统的吞吐量,同时提供事务级一致数据。由于快照保证了读取一致性——仅限于语句——因此不可能出现长期运行冲突。此外,在这种环境中应用程序更改也是不必要的。
当使用了悲观锁定(ANSI 的事务隔离标准)时,应用程序通常表现出阻止。同时事务内的读取器和编写器的数据访问请求会请求冲突锁定;这完全是正常的,所提供的阻止是短期有效的,而不是显著的性能瓶颈。这可能会在压力下更改系统,因为事务处理时间的任何增加【例如过度利用系统资源(比如说磁盘 I/O、RAM 或 CPU)所导致的延迟,以及编写不好的事务(比如那些具有用户交互的事务)导致的延迟】都会不成比例地影响阻止——事务执行时间越长,锁定的时间就越长,阻止的可能性就越大。
这种情况的一个示例是一家轿车租赁公司,这家公司使用一个内部基于 web 的预定应用程序来代顾客预订轿车。类似这种系统一般具有争用相同数据(例如汽车)的事务。这种系统将会提供短期查询,以便客户服务代表在为客户预订汽车之前可以查询某地汽车的可用性,这就是编程技术(例如断开数据集)可以提供最佳并发控制的领域,具体说来就是:
1. |
该应用程序可以在租赁地点的特定数据范围内查询某一类型的所有可用汽车。这种查询可能是最少几个表的集合,例如汽车、类型、预定状况和地点。此外,这种查询将会运行在“读取已提交”隔离级别下,以确保仅向用户返回已提交的数据。 |
2. |
通过这种查询获得的记录集将会从数据库上“断开”,以便在数据显示在调用者的应用程序中时解除所有对数据的锁定。这通常被称为“成批乐观”,因为它模拟了乐观形式的数据库并发控制。它是乐观的,因为尽管数据是实时的,但冲突的可能性很低。行级时间戳的使用使得编程人员可以通过向用户界面发送适当的消息来识别数据更改和管理冲突。 |
3. |
预定客户选择一辆汽车,而系统将会对数据集进行编辑以反映这次预定。 |
4. |
然后,应用程序会重新连接数据库,并尝试同步数据库更改,同时使用行级 SQL Server 时间戳列来确保数据在断开时没有被其他预定客户所更改。 |
5. |
然后,应用程序会向预定客户报告预定成功(系统接受了预定)或是说明存在冲突(这辆车已经被其他客户预定了),并提供预订另一辆车的机会。 |
请注意,上述技术并不是真正乐观的。在这种设计模式中,大量争用可能会在步骤 1 中的查询查找候选汽车时发生。使用“SQL Server 2005 读取已提交隔离级别”(RCSI),系统会在查询运行的同时为这些请求提供一个非锁定、非阻止、事务一致版的数据。使用这种隔离,可以减少服务器的负载,并且不会对其他想要预定汽车的客户阻止数据。尽管这会提高预定汽车事务的性能,但它并不能真正提高长期运行查询所看到的汽车可用的机会。不过,这是一个可以接受的平衡。预定进行得很快,并且不会受到同时发生的轿车租赁请求的阻止。这增加了事务吞吐量,尤其是在高峰工作负载下,例如节假日预定和商务旅行高峰期间出现的预定高峰。
一旦数据库管理员在数据库级别启用 RCSI 后,在上述步骤 1-5 中使用的编程逻辑不用进行更改就可以从这种新行为中获得好处。事实上,在进行数据库设置后,所有查询都将默认采用这种形式的语句级读取一致性。
针对实时数据的特殊报告
在不懈扩展信息系统功能的同时,各个公司也都在努力降低成本。SQL Server 2005 的指导目标之一就是减少数据库中的数据在被捕获和被组织利用之间的这一段延迟,这种延迟的降低使得开发人员可以构建可提供传统成批报告计划之外的数据的系统。
我们来看看一个食品零售商的案例,他们需要减少每个商店里快速消费品(例如三明治、牛奶和其他容易过期的食品)的库存,但超级市场的货架上还要摆放着客户想要购买的商品,这家食品零售商正在努力平衡这两种需求。这些商品非常容易受到天气的影响,例如烤肉和冰淇淋在晴天的销量较大;而安慰食物在雨天的销量更大。
图 1:钱柜被报告用户阻止
在新的隔离级别被引入之前,超级市场应用程序的开发人员应当避免通过使用“读取未提交隔离级别”(这可能难以使用,尤其是在加入多个表时)长期阻止实时数据。但是“读取未提交隔离级别”提供了对数据库的语句级事务一致性视图的非阻止访问,在这种视图中有关商业事务的数据可能只有一部分到达数据库。
而且,在对销售数字进行分析以便从已销售商品中找到销售最快的商品时,由于数据继续到达数据库,这种分析可能是非常数据密集型和长期运行的操作,从而导致更有可能产生不一致的机会。
在需要事务一致性数据视图的环境中,系统设计者通常会设计这些类型的报告用完所有时间,以避免影响实时系统的并发(在这种系统中,在使用高峰期启动的长期运行、只读的报告可以结束阻止所有编写器更新系统,如上述图 1 所示)。
拥有一个只提供预先规划的隔夜报告的 IT 基础结构可能会妨碍超市管理人员应对未知需求和查看哪些产品可能难以卖出的能力,因而错失定购第二批货物以补充库存的机会,导致收入损失或甚至导致更坏的情况——客户流失。
新的隔离级别为应用程序提供了非锁定事务访问或整个数据库的语句级事务一致性视图,从而使得报告编写器的工作更为轻松和获得更多回报。在 SQL Server 2005 中,数据库引擎的功能与 Analysis Services 的高级报告功能更紧密地集成在一起,Analysis Services 引入了“通用数据模型”,从而可以进行完整的分析报告,而不用提取数据并将数据转换为星型架构。“快照隔离”技术在令数据更易于为这类应用程序访问的过程中具有一个主要的功能——它可以结合交叉销售报告和实时数据,这使它可以改变这些业务过程工作的方式。
• |
重新工作的 Read Committed Snapshot Isolation 非常适用于现有报告系统(或者从第三方处购买的系统,这种系统不能更改隔离级别),因为可能不需要进行应用程序更改就可以利用非锁定读取,尤其是因为大部分这种应用程序都使用一个查询的结果来填充报告。 |
• |
新的 Snapshot Isolation Level 适合用于更复杂的要求,例如运行一系列可以在相同事务内运行的报告,因此所有报告都可以看见相同的事务一致性数据视图——这种情况更可能出现在复杂的财务报告系统中,在这种系统中不希望在报告套件运行的时候进行数据更改,因为它很容易会引起各个报告之间总计和总数的异常。 |
SQL Server 2005 使得我们可以方便地对数据库使用这些新的隔离级别。一旦新形式的 Committed Snapshot Isolation 配置完毕,SQL Server 就会自动使用它,而不用进行任何应用程序或事务代码更改。为了利用事务级快照隔离,您必须在任何快照事务开始前配置连接的隔离级别。
一旦启用了其中一个功能,我们就可以安全地向超市管理人员提供一系列参数化的报告,这些报告可以在商店里产生意外需求时运行——而不用阻止来自商店钱柜的数据进入,从而可以帮助管理人员确保满足客户需求,最终产生较高的客户满意度。但是什么功能最适合超市?Snapshot Isolation 适合用于通过一些查询得出的复杂报告。使用这种隔离级别可以保证报告中所有元素的一致性,因为数据版本的事务长度是一致的。如果报告总是基于一个查询,那么新宠 Read Committed(使用行版本控制)会是一个更好的选择,因为仅需要为单个选择语句的有效期维护行版本。
支持 Snapshot Isolation 对数据库服务器产生了其他需求。在上述讨论的情境中,我们假定用来收集超市钱柜数据的后端办公服务器具有足够的空闲容量来支持意外的要求,以针对实时数据运行特殊的报告。使用快照隔离会给正在运行更新事务服务器增加负载,负载增加同时作用于数据编写器和数据读取器。对于数据编写器,它们的更改必须记录版本。对于数据读取器,它们的读取必须在版本链中移动,以便获取相应于它们的事务启动时间的正确版本。
还会有其他负载作用于 TempDB(因为 TempDB 是 SQL Server 存储版本信息的地方,它用于提供正在更改的数据的事务一致性视图),因此我们建议在部署到生产系统之前,DBA 应当在预生产系统中使用模拟负载来测试这种新技术。请注意,简单的措施(例如为 TempDB 提供更多 I/O 带宽)可能会比启用 Snapshot Isolation 的影响更偏移。但是,如果系统承受了繁重的更新和读取负载,那么在其他情境中讨论的配置(参见下文)可能更为合适。当要求使用事务范围 Snapshot Isolation 而不是语句级 Read Committed Snapshot Isolation 时,情况尤其是这样。
针对副本托管数据库的特定报告
在具有高百分比数据更改的系统中,允许使用新的 Snapshot Isolation Level 对于整体性能可能会有消极影响,因为创建和管理行的以前版本的开销可能会减缓事务,尤其是在 TempDB 或磁盘子系统已经接近系统瓶颈的时候。在这种情况下,考虑到启用新结构的性能代价,这可能不会比针对实时数据的报告有价值,特别是因为即使对已经很繁忙的系统增加更多负载,报告也可能添加进来。
这种情况对于预定系统(例如航空和宾馆预定系统)以及订单输入系统——包括一些联机系统(例如 web 商店站点)——都很典型。负载高峰期间更新的性能非常关键——一次稍微迟缓的更新可能会导致客户放弃他们的购买,并转向其他站点;相反地,客户服务部门和需求预测职员应当访问包含实时数据的报告,以帮助他们与客户交互和进行规划。
要满足这些冲突的要求,最好是建立一个副本托管数据库——一个位于实时系统后面的接近实时的数据副本,但“实时程度足够”用来提供报告。这种副本的目的就是将报告用户的负载转移到另一台服务器(甚至是一组服务器),这样他们就不会增加实时系统的工作负载。
SQL Server 2005 提供了两种自动维护副本数据库的选择,这两种选择都运行在事务记录机制内,因此在已提交的数据上:
1. |
Database Mirroring(也叫作 Synchronous Log-Shipping)——这种技术的主要设计目的是提供实时系统的热备用:数据在每次事务提交过程中被发送给副本——要等到数据位于实时数据库和副本数据库日志中之后,提交才会完成。实时系统的性能对备用系统提交的能力很敏感。因此,Data Mirroring 不太适合用于转移报告负载,因为报告负载中的峰值可能会直接影响实时系统的性能。相反,数据库监视更应当视为是一项可用功能而不是辅助的报告数据库。 Data Mirroring 的主要优点是它非常易于设置和管理、数据库管理员不用选择特定表就可以转移所有数据(事实上,在对实时系统进行更改的同时,这些更改就会自动出现在副本中)以及副本服务器数据库快照的使用可以用于提供报告时间点。但是数据库快照必须人工创建。为每个报告维护一个视图点可能是不现实的(假定要求访问最新数据)。 其他缺点包括不能单独对副本进行更改(例如过滤数据的子集;添加具有只读权限的只报告用户;以及添加其他设计用于辅助报告功能的表索引和索引视图)。这些更改只能对实时系统实施,结果更新性能会有所下降。 |
2. |
Replication(尤其是 Transactional Replication)——这种技术仅对实时系统产生较小的开销,开销的减轻是通过改进数据库日志文件 I/O 带宽实现的。已提交的事务被异步从数据库事务日志文件中读取,数据被迁移到分布数据库,并从分布数据库中被分发给多个订阅者。这种技术可能更难以管理,但是却为数据库管理员所熟知,因为它一直是许多年来 SQL Server 的核心组件。被复制的数据可以是实时系统的子集(按表以及按行和按列),并且具有允许不同用户、索引和视图出现在订阅者(报告)数据库中的优点。 缺点很少——需要在分发数据库中维护一个数据副本,直到它被传播给所有订阅者。 SQL Server 2005 在复制对象上的架构更改要求重做类别(以及使用特定于复制的存储过程来添加/删除列),这是以前版本要求的。以前,当应用程序没有使用复制命令来更改各个版本的架构时,这些命令限制使用来自第三方应用程序的复制。图 2 说明了两台服务器中的典型的事务复制设置。 |
在过去,使用事务复制的主要问题在于分发/订阅数据库链接也存在与实时系统相同的问题。当长期运行报告在订阅者数据库中运行时,它们会阻止复制数据到达订阅者系统。反过来,这种阻止又会导致副本不断脱离同步和滞后于实时系统。而这种情况又会反过来令尝试向客户提供最新购买或预定的呼叫中心职员受挫。通过新的 Snapshot Isolation 和重做的 Read Committed Snapshot Isolation 可以解决这个问题。
订阅者数据库可以被设置为使用 Snapshot Isolation,而依赖于该数据的报告(和只读应用程序)可以使用 Snapshot Isolation(以获得一套报告/对话的一致性视图)或 Read Committed Snapshot Isolation 以获得个别报告。这些应用程序都不要求共享锁定,这会防止最常读取的数据库远远位于实时系统后面。传入的数据不会被阻止在长期运行读取事务后面,并且将会针对数据库的事务一致性视图执行查询。此外,复制可以在系统中移动数据时更好地保持事务逼真度。
这也是一个非常可缩放的解决方案。随着报告负载的增加,我们可以在新服务器上增加另外一个(或更多)订阅者数据库,以便处理额外的负载而不会进一步影响实时系统。现在客户服务代表在访问副本以根据需要帮助客户时,应当可以看到客户(在实时系统中)生成的事务。
针对实时数据的隔夜报告
这种情境考虑了典型的“联机日”和“隔夜批处理”的数据处理模型。“Online Day”匹配一组典型的已定义“业务时间”,其中数据使用全部由较短事务组成的负载被输入系统。“Overnight Batch”则是长期运行报告移动和针对在白天到达的数据的报告。这种情况在主机应用程序中非常典型,在这种应用程序中,TP 监视器在白天运行,而批处理作业则在夜间运行。
面向客户、支持 internet 的应用程序的增长以及不断增加的全球化公司(办事处搬到网上,也有其他公司搬到网下)意味着这种模型与现代数据中心的相关性较小。但是,通过研究这种旧有的技术,还是可以学到一些道理:
1. |
用户中心的负载会有高峰和低谷。 |
2. |
报告适合在某些时间运行,以便在该时间点上保持一致。 |
3. |
多数数据库的负载具有更新高峰(例如数据加载)和读取高峰(例如报告)。 |
我们来看看“Gadget.com”,这是一家面向 internet 的公司,这家公司支持和销售个人音频技术;这家公司在纽约有一个数据中心,该中心服务于它的全球业务以及遍布 7 个国家的小型办事处。和大多数公司一样,这家公司的联机系统具有明确的使用模式。在这里,高峰负载符合美国模式——员工达到办公室,主要客户达到 web 站点:
事务处理系统工作负载
时间(东部) | 业务事件 | 数据中心事件 |
08:00:00 上午 AM |
美国办事处联机 |
任何剩下的报告被暂停 |
12:00 PM |
所有美国办事处联机,欧洲办事处关闭 |
高峰办事处负载 |
18:00:00 上午 AM |
美国办事处开始关闭,小型的亚洲和澳大利亚办事处联机。 |
高峰联机负载 |
22:00:00 上午 AM |
? |
最低办事处和联机负载 Snapshot Isolation 被启用,数据提取开始,然后开始主要的运作报告套件 |
02:00:00 AM |
欧洲办事处联机 |
Snapshot Isolation 被禁用,一些特定于美国的运作报告继续 |
在上述情境中,数据中心可以在数据库联机时管理“快照隔离”的状态;不需要重新启动数据库就能应用不同设置。仅仅通过在一个窄窗中激活 Snapshot Isolation,Gadget.com 就可以继续向联机和全球办事处用户提供服务,同时确保长期运行报告不会阻止这些用户。通过在使用高峰期间禁用 Snapshot Isolation,Gadget.com 同时确保了可以向它的主要用户和客户提供最大的吞吐量。
Gadget.com 同时还运行一个复杂的数据仓库,用于提供有关客户和库存趋势的信息,以及用于运行更加大型的报告,以便在随着时间增加的数据中查找其他模式。该系统主要是只读的;但是只有有限数量的用户需要更新对于数据库的访问,以便进行以下审核的帐户类型记录调整和库存调整。这对于 Gadget.com 来说并不是一个问题,因为他们已经适应了他们对此系统的需要使用 Snapshot Isolation 的策略。
数据仓库系统工作负载
Gadget.com 运行他们的数据仓库系统 24x7;他们使用 Snapshot Isolation 来向他们的报告消费者提供事务一致性数据的高性能访问。
时间(东部) | 业务事件 | 数据中心事件 |
08:00:00 上午 AM |
美国办事处联机 |
高峰联机报告负载 |
12:00 PM |
所有美国办事处联机,欧洲办事处关闭 |
高峰调整负载(但很微小) |
18:00:00 上午 AM |
美国办事处开始关闭,小型的亚洲和澳大利亚办事处联机。 |
报告仍然联机 |
22:00:00 上午 AM |
? |
最低报告负载 数据库被设置为简单恢复模式。报告应用程序对传入的报告请求进行排队,数据负载以及之后的数据转换开始。 |
02:00:00 AM |
欧洲办事处联机 |
系统被设置为完全恢复模式。一次完全数据库备份开始(为速度提供适当的硬件支持),长期运行和排队的报告开始,接着结束,特殊工作负载开始。 |
Gadget.com 查看他们每天需要进入系统的数据量,并决定结合使用“完全”和“简单”恢复模式来最大化负载的性能。“完全”恢复模式用于保护仓库管理员对数据所做的特殊调整。简单恢复模式用于在装载数据时减少日志记录以及要求的日志维护(尽管更小)。对于数据装载,不需要更改 Snapshot Isolation 设置,因为行插入不会生成版本链项。Snapshot Isolation 以 24x7 的方式运行,它允许快速的数据装载和特殊数据调整可以继续,而不会受到可能阻止数据装载过程的长期运行报告的影响。所做的唯一操作调整就是调整恢复模式以减少日志记录和改进数据装载:
• |
数据装载期间的简单恢复模式(由于有了先前的备份,系统处于完全可恢复状态,它的相关日志和传入的数据提取文件。通过将恢复模式更改为“完全”,然后执行一次完全数据库备份,负载就被承接下来。 |
• |
“完全恢复”模仿剩余的时间,允许进行日志备份,这样会计调整就不会因为硬件故障或介质损坏而丢失。 |
上述情境介绍了如何使用不同的事务工作负载(联机事务处理和数据仓库)将 Snapshot Isolation 部署到系统中。由于 Snapshot Isolation 可以保持活动状态,因此可以实现它的优点,而不会对系统的任何关键活动产生重大影响,这强调了这项技术的实用性。
迁移到一般数据库技术
在商业关系数据库管理系统领域中,先于 SQL Server 2005 出现的有两个阵营。第一个阵营是基于锁定构架实现悲观并发的系统,它支持 SQL-92 标准(ANSI X3.135-1992,美国国家信息系统标准 — 数据库语言 — SQL,1992 年 11 月)中定义的四个 ANSI 标准隔离级别 – 这些系统包括 Microsoft SQL Server、IBM DB2(所有代码基础/平台)和 Sybase Adaptive Server。第二个阵营基于保留事务启动时的数据视图来实现非标准的事务隔离模式 – 这个阵营中只有一个系统,就是 Oracle。
这种划分产生了三种类型的软件开发商:
1. |
在 Oracle 上开发,端接到 Microsoft SQL Server |
2. |
在 Microsoft SQL Server 上开发,端接到 Oracle |
3. |
为两个阵营开发和优化 |
一般说来,只有大型软件公司才能够承担“类型 3”,例如 SAP、Siebel 和 Peoplesoft。大多数开发商必须选择类型 1 或类型 2,他们的选择通常建立在 Unix 市场对他们销售的影响程度的基础上。
借助于 SQL Server 2005 以及 Snapshot Isolation 引入的乐观并发控制,类型 1 应用程序供应商现在可以很容易地直接端接到 SQL Server,从而将他们的市场扩展到 Oracle/Unix 平台上。想要降低支持多个数据库平台的复杂性以及想要避免如下成本的 IT 部门:
• |
多个数据库团队 |
• |
增加的培训成本 |
• |
降低的批量软件许可成本 |
• |
与多个供应商打交道的管理时间 |
• |
匹配不同的供应商服务水平 |
SQL Server 2005 使得客户可以减少这些额外的成本,而不用改变应用程序供应商或担心事务隔离模式的习惯转变引起的优化性能下降。
在 SQL Server 2005 和 Oracle 中乐观并发的实现具有广泛的不同 – SQL Server 实现的设计使得数据库管理员可以更容易控制它(可以在命令中启用和禁用它,如前面的情境所述),同时更加容易管理 – 通过系统功能可以访问丰富的“Windows 系统监视器”性能计数器和虚拟表,这可以帮助检测和解释数据库中发生了什么。
SQL Server 和 Oracle 在快照上的不同之处
Microsoft SQL Server 2005 | Oracle | ||||||||||
不需要进行表修改 |
在使用 SERIALIZABLE 前需要使用 INITRANS >= 3 & MAXTRANS on CREATE/ALTER TABLE DDL 来启用页面上事务信息的空间。 | ||||||||||
版本存储被保存在 TempDB 中。DBA 必须确保基于版本存储工作负载为改进的 i/o 带宽优化 TempDB,因此必须监视 TempDB 数据库大小,很多版本的 SQL Server 都支持百分比和绝对数据库/日志自动增长设置,但这些显然受到磁盘物理可用性的限制。 |
要求 ROLLBACK SEGMENTS(定义和联机/脱机)的复杂配置和事务级别 USE ROLLBACK SEGMENT 语句以避免 ORA-01555:“长期运行”事务导致的“快照太旧。”覆盖它们在回滚部分的版本控制页。注意:Oracle 没有“长期运行”事务的定义。 | ||||||||||
TempDB 可以按照当前大小的 %(以便弹性地减少自动增长尝试的次数)或是按照某个绝对值来自动增长。 |
ROLLBACK SEGMENTS 不支持 PCTINCREASE,因此也不会“自动增长”,所以您必须在创建它们时取得大小。 | ||||||||||
基于行的数据版本控制 – 写入版本存储/从版本存储中读取的数据量更少,行级版本控制意味着事务化数据访问的真正行级顺序 |
基于页的数据版本控制 – Oracle 必须重新构建整个页。当其他事务使用 SERIALIZABLE 访问已更新页上的其他行时会导致 ORA-08177:"Can't serialize access for this transaction." | ||||||||||
Snapshot Isolation 和 Read Committed 在数据库级别上被启用。只有要求此选项的数据库需要启用它,并产生与它相关的开销。您必须使用快照隔离在各个将要参加交叉数据库事务的数据库中启用它。 |
数据版本控制不是可选的;它总是被启用。 | ||||||||||
大量的操作“性能计数器”,允许 DBA 监视版本存储的状态,包括:
|
Perfmon 计数器 | ||||||||||
虚拟表允许 DBA 查看快照事务是否已产生,以及版本存储的大小和版本存储中的最早记录:
sys.dm_tran_version_store() |
虚拟表 |
Oracle 和 SQL Server 2005 之间既有如上所述的不同之处(这些不同是为了方便数据库管理员的工作),也有相同之处(为了方便开发人员将应用程序从 Oracle 端接到 Microsoft SQL Server 2005)。
SQL Server 和 Oracle 在快照上的相同之处
Microsoft SQL Server 2005 | Oracle |
SELECT ( WITH (UPDLOCK) 等价项,立即执行冲突检查 |
SELECT( FOR UPDATE 用于锁定事务中的记录以防止冲突 |
READ COMMITTED(带有行版本控制) |
READ COMMITTED |
SNAPSHOT |
SERIALIZABLE |
SNAPSHOT |
READ ONLY |
READ UNCOMMITTED(访问未提交的数据) |
没有等价项 |
READ COMMITTED(锁定) |
没有等价项 |
REPEATABLE READ |
没有等价项 |
SERIALIZABLE |
没有等价项 |
可以在乐观隔离级别中使用锁定或必须处理冲突(数据行在事务外更新)并重新尝试已经失败的事务。行级版本控制减少了冲突的机会。 |
必须处理冲突(ORA-08177:数据页在事务外更新)并重新尝试已经失败的事务。 |
应用程序可以选择合适的并发模式。 |
应用程序总是看见可能的陈旧数据,因为在并发模式之间没有选择。 |
Transact-SQL TRY/CATCH 逻辑处理冲突错误,但不会处理 TempDB 的空间问题。 |
PL/SQL 具有错误处理,它支持 ORA-08177(冲突)错误处理,但不能处理 ORA-01555(回滚部分空间问题)。 |
以这些相同点为基础,SQL Server 2005 使得应用程序(其构建目的是为了针对支持乐观并发的数据库运行)的端接显著比以前的版本更加容易。此外,SQL Server 2005 引入了编程模型,该模型允许在悲观和乐观并发控制之间选择,同时还有多个实现机制。简单和容易配置的联机版本存储使得数据库管理员的工作更加方便,只要在数据库级别启用,开发人员在端口代码方面的任务就会更加简单,因为 Oracle 和 SQL Server 2005 架构之间的关闭功能是匹配的(尽管 SQL Server 2005 表现出更多的粒度一致性行为,因为它在行级控制版本3而不是在页级)。
理解并发控制
就像在使用情境中看到的,在控制并发时主要使用两种模型:悲观并发和乐观并发。在基于悲观并发控制的系统下,锁定被用于阻止用户以影响其他用户的方式修改数据。一旦应用了锁定,其他用户就不能执行可能与锁定冲突的操作,直到所有者释放锁定。这种级别的控制通常用于存在较多数据争用的环境,以及如果/当并发突出产生时使用锁定来保护数据的成本小于回滚事务的成本的环境中。相反,在基于乐观并发控制的系统中,用户在读取数据时并不锁定数据。在执行更新时,系统会检查是否有另一个用户在数据被读取后更改了数据。如果另一个用户更改了数据,就会产生一个错误。通常,用户收到错误后会回滚事务,重新提交(应用程序/环境相关)和/或启动。这被称为乐观并发,因为它主要用于低数据争用的环境中,以及读取时偶然回滚事务的成本超过锁定数据的成本的环境中(请注意,在 Read Committed 下执行的更新和 Snapshot Isolation 下执行的更新不会冲突,因此不会导致回滚成本)。
在 SQL Server 2005 之前,事务是以悲观方式控制的,这意味着所有事务都获得锁定。尽管锁定是多数应用程序的最佳并发控制选择,但是它也会导致编写器阻止读取器。如果某个事务更改了某一行,那么在编写器提交之前另一个事务就不能读取该行。在一些情况下,等待更改完成是正确的响应;但在另一些情况下,以前的行事务一致性状态就足够了。
基于快照的隔离级别使得读取器能够取得先前提交的行值,代价是在修改行时必须保留此版本——即使“当前”没有人正在访问数据也是一样。这意味着所有选择、更新和删除(但不插入)语句可能都要付出版本控制的代价(进出版本存储的额外的 I/O)。您必须作出决定,使得这一交换可以在开销和性能的代价下获得更好的并发。要说明的很重要的一点是,尽管可能需要花费更多代价来执行每次查询(因为版本控制),但是最终的结果可能是您可以支持更多的吞吐量(因为争用降低了)。它可以应用在争用消耗吞吐量的环境中,这很重要;如果您使用这种技术作为不是由争用引起的性能问题的解决方案,那么您可能就是在解决错误的问题,并且实际上,会降低您的系统吞吐量。
一般说来,当数据库通过基于版本控制的隔离自动控制您的数据视图时,应用程序编程会比价容易。在这种环境中,您较少担心死锁和阻止,而是在管理性管理开销和性能上额外付出了一点代价。在很多情况下,对于 TempDB 来说可能更容易选择在管理性开销和提供更多磁盘吞吐量上付出代价。如果所有基于查询的快照都只能提供读取一致性,而不能提供后续修改的基础;那么就不需要任何应用程序重试逻辑。但是,您最终可能会在使用快照隔离级别并且后来执行了更新的事务中遇到冲突;如果版本是“陈旧的”,那么您可能需要使用重试逻辑进行更新。
在使用了阻止来控制资源访问的环境中(例如在表中实现的查询),如果您启用了 Read Committed Snapshot,那么您就必须使用 WITH (READCOMMITTEDLOCK) 锁定提示来获取预期行为,因为基于快照的读取将永远不会阻止。
编程人员现在可以选择使用 SQL Server 2005 冲突解决方案和/或 Transact-SQL 事务错误处理来代替以前的时间戳管理技术。此外,当您的工作负载是由成批类型的更新(其中很多行被修改)组成的时,不建议您使用快照隔离,因为冲突的机会可能会显著增大。在这种情况下,您应当选择一种基于锁定的隔离级别(READ COMMITTED、REPEATABLE READ 或 SERIALIZABLE),保持您的事务简短,然后小心设计您的事务以尽量减少资源冲突,从而尽量减少死锁。
理解隔离
由于“隔离级别”在 SQL Server 2005 中完全可以控制,因此理解对于您的应用程序来说最合适的隔离——以便在保持适当水平的精确度的前提下获得最佳并发和性能——就很重要。“隔离级别”并不是一个新概念,事实上,您就可以在以下网站找到 ANSI 有关隔离规范的详细资料:www.ansi.org 当前可以参阅的规范是 ANSI INCITS 135-1992 (R1998)。这个标准是独立于实现的,但是这样一来,在一致性和性能的平衡以及如何实现这些目标和标准等方面却多少显得有点含糊。因此,人们撰写了很多文章来进一步阐述这些标准:Generalized Isolation Level Definitions 或者甚至批评它们——比如说 The Critique of ANSI Isolation Levels。根据这些作品中提出的批评分析,以及 ANSI 标准中含糊不清的规范,SQL Server 2005 提供了很多具有代表性的可能的组合。
SQL Server 2005 中提供的隔离级别
隔离级别 | 脏读(可能的情况) | 非可重复读取(可能的情况) | Phantom(可能的情况) | 并发控制 |
读取未提交 |
是 |
是 |
是 |
(无) |
读取已提交 |
否 |
是 |
是 |
悲观 |
读取已提交快照 |
否 |
是 |
是 |
乐观 |
可重复读取 |
否 |
否 |
是 |
悲观 |
快照 |
否 |
否 |
否 |
Optimistic |
可序列化 |
否 |
否 |
否 |
悲观 |
上述每种情况下使用的应用程序根据希望级别的“正确性”和在性能和管理性开销上的平衡选择而有所不同。
隔离级别和应用程序最合适
隔离级别 | 在以下情况下最适合于应用程序: |
读取未提交 |
应用程序不要求数据的绝对精度(可能得到大于/小于最终值的数值),并且相对于所有其他要求来说更看重 OLTP 操作的性能。不要求版本存储,不要求锁定,没有被授权任何锁定。这种隔离中查询的数据精确度可以看到未提交的更改。 |
读取已提交 |
应用程序不要求长期运行集合或长期运行查询的时点一致,但要求被读取的数据是事务一致的。应用程序不希望用版本存储的开销来平衡长期运行查询的可能的不正确,因为没有可重复读取。 |
读取已提交快照 |
应用程序要求长期运行集合和/或长期运行查询的绝对的时点一致性。所有数值在查询开始时必须是事务一致的。数据库管理员选择应用程序的版本存储开销,以便获得由于锁定争用减少而带来的吞吐量增加的好处。此外,应用程序需要大型查询(而不是事务)的事务一致性。 |
可重复读取 |
应用程序要求长期运行多语句事务的绝对精确度,并且在事务完成之前必须保留来自其他修改的所有请求数据。应用程序要求所有在此事务中被重复读取的数据的一致性,并且要求不允许进行其他修改——如果其他事务尝试更新读取器锁定的数据,这可能会影响多用户系统中的并发。当应用程序依赖于一致性数据,并且计划稍后在相同事务中修改数据时,这种级别是最合适的。 |
快照 |
应用程序要求长期运行多语句事务的绝对精确度,但不计划修改数据。应用程序要求所有在此事务中被重复读取的数据的一致性,但仅计划读取数据。不需要使用读取锁定来防止其他事务的修改,因为要等到数据修改事务提交或回滚,并且快照事务结束之后才可以看到更改。可以在此事务级别中修改数据,但是有可能与在快照事务启动后更新相同数据的事务产生冲突。这种冲突可以由每个更新事务来处理。具有多个读取器但只有一个编写器的系统(例如上述情境章节中的“复制的报告系统”)不会遇到冲突。 |
可序列化 |
应用程序要求长期运行多语句事务的绝对精确度,并且在事务完成之前必须保留来自其他修改的所有请求数据。此外,事务会请求一组数据而不仅仅是单独的几行数据。每组数据都必须在事务内的每个请求中产生相同的输出,并且对于预期的修改,不仅不能有任何用户可以修改已读的数据,而且还必须防止向组中输入新行。当应用程序依赖于一致性数据,计划稍后在相同事务中修改数据,并且即使在事务的最后都要求绝对的精确度和数据一致性时(在活动数据内),这种级别是最合适的。 |
快照隔离考虑事项
尽管在快照下更改到“读取已提交”不要求进行应用程序更改,但它却要求管理更改。启用数据库的快照隔离支持要求进行管理性规划,可能还需要应用程序规划。在这两种情况下,快照选项是在数据库级别启用的,而在所有情况下,行版本控制都存储在 TempDB 中。
定义、术语和语法
若要在 SQL Server 2005 中实现快照隔离,您必须熟悉一些新的概念、术语和语法。在以前的版本中,“隔离级别”是通过一个会话设置 (SET TRANSACTION ISOLATION LEVEL) 或查询提示 (FROM 表名 WITH (隔离提示)) 单独控制的。在 SQL Server 2005 中,若要使用快照隔离,必须设置好(而不是待定)这两种受支持的数据库选项之一。如果请求使用快照隔离,但数据库还不能够处理快照(比如说仍处于待定),那么请求快照的语句将会失败。关键是要在合适的时间进行更改——如果来回更改——以及了解更改时数据库和客户端请求的状态。
为了使用行版本控制,您必须首先确定您的应用程序要求使用哪种级别的隔离。SQL Server 2005 支持两种类型的快照隔离:语句级快照和事务级快照。
读取已提交与快照隔离(语句级快照)
设置事务级快照可以确保读取已提交隔离下的每个语句仅看到语句开始之前发生的已提交更改。事务内的每个新语句都会选取最近的已提交更改。版本“刷新”会发生在每个 SELECT 语句开始的时候。换句话说,这种版本的读取已提交从语义上来说是相同的,因为只有已提交更改可以看到,但是这些更改提交的时间则是不同的。每个语句都可以看到在语句开始之前(而不是在资源被读取时)提交的更改。换句话说,这完全是一种新的非锁定、非阻止的读取已提交,它创建了一个固定的时点,在这个时点上数据是精确的——与语句开始时的精确度一样。
语句级快照可以通过打开 READ_COMMITTED_SNAPSHOT 数据库选项来启用。一旦启用之后,就不再需要任何其他应用程序更改。
语法:
ALTER DATABASE <databasename> SET READ_COMMITTED_SNAPSHOT ON WITH <termination>
执行这个语句要求一个对于数据库的用户会话访问。使用 ALTER DATABASE WITH <termination> 选项来终止数据库中的其他用户会话和回滚它们不完整的事务。理想情况下,这种更改将不会需要很长时间,并且它可能是永久更改。若要查看数据库是否设置了这个选项,可以使用 sys.databases 系统视图。
语法:
SELECT sd.is_read_committed_snapshot_on FROM sys.databases AS sd WHERE sd.[name] = <databasename>
is_read_committed_snapshot_on 的返回值可能是 (1) 或 false (0)。当 Read_committed_snapshot 选项被打开时,“读取已提取快照隔离”下的读取操作根据快照扫描而定,并且会在一个非锁定模式下执行。当 Read_committed_snapshot 选项被关闭时,“读取已提取快照隔离”下的扫描会在短期锁定模式下执行,在这种模式下,锁定仅会在读取请求的有效期内保持有效。
快照隔离(事务级快照)
设置事务级快照隔离可以保证——默认——快照隔离事务中的每个语句仅看到事务开始之前发生的已提交更改。事实上,事务中的每个语句都看到相同数据组;而此事务之外的修改则可以使用数据。修改不会被阻止,而此“快照”事务也不会知道更改。只有在事务级快照语义下运行事务,版本“刷新”才会在每个事务开始的时候发生。如果您使用 READ COMMITTED 提示覆盖了事务,那么就会发生锁定(可能还有阻止)——除非您同时打开新的“读取已提交快照”。如果使用了新的“读取已提交快照”,那么您将使用行版本控制向查询返回数据——除非您用新的 READCOMMITTEDLOCK 锁定提示覆盖了它。
请注意,对于数据库目录的 DDL(数据定义)更改会立即影响在“快照隔离”下运行的事务。
为了获取事务级快照,需要进行两种更改。首先,必须打开 ALLOW_SNAPSHOT_ISOLATION 数据库选项,以使数据库允许进行更改。其次,应用程序/用户必须请求一个快照事务。
允许快照隔离
管理员必须设置一个允许快照隔离的数据库选项。但是,这个数据库选项可能不会立即生效;而且可以在用户连接到数据库时对它进行更改。如果用户正在处理事务时进行了状态更改,那么要等到所有事务都完成后,快照事务才可能发生(因为还没有为当前运行的那些事务维护行版本)。如果更改状态花费了很长时间但仍在运行,而事务在数据库仍然处于“待定状态”时尝试运行快照事务,那么它们将收到一个错误。如果在进行更改时正在执行长期运行事务,那么对于版本控制状态的更改就可能需要很长时间才能完成。如果所需的 DBA 可以取消该请求,并且如果取消了,版本控制状态将会回滚到之前的版本控制(或非版本控制)状态。若要请求数据库的快照隔离,请使用 ALTER DATABASE 更改数据库状态:
语法:
ALTER DATABASE <databasename> SET ALLOW_SNAPSHOT_ISOLATION ON
若要查看这个选项是否已经生效,您可以检查 the sys.databases 系统视图。其中有两列您可能会感兴趣:snapshot_isolation_state 和 sd.snapshot_isolation_state_desc。snapshot_isolation_state 返回一个介于 0 和 3 之间的值:
0 = 快照隔离“关闭”
1 = 快照隔离“打开”
2 = 快照隔离状态正在转为“关闭”状态
3 = 快照隔离状态正在转为“打开”状态
snapshot_isolation_state_desc 返回一个 nvarchar(60),它是这种待定状态的一个字符描述。
OFF = 快照隔离“关闭”
ON = 快照隔离“打开”
IN_TRANSITION_TO_OFF = 快照隔离状态正在转为“关闭”状态
IN_TRANSITION_TO_ON = 快照隔离状态正在转为“打开”状态
语法:
SELECT sd.snapshot_isolation_state , sd.snapshot_isolation_state_desc FROM sys.databases AS sd WHERE sd.[name] = <databasename>
Snapshot Isolation state | 描述 |
关闭 |
数据库中禁用快照隔离状态。换句话说,不允许存在基于快照隔离级别的事务。在重新启动恢复时,数据库版本控制状态初始时被设置为“关闭”(一个新的 SQL Server 2005 功能是,数据库在 REDO 阶段的恢复后即可用)。如果启用了版本控制,那么在恢复完成后,版本控制状态会被设置为“打开”。 |
PENDING_ON |
正在启用快照隔离状态的过程中它会等待所有在 ALTER DATABASE 命令发出时处于活动状态的更新事务完成。这个数据库中的新的更新事务要生成记录版本,因此需要进行版本控制。快照隔离下的事务不能启动。 |
开启 |
启用了快照隔离状态。可以在这个数据库中启动新的快照事务。 在版本控制状态“打开”之前启动的现有快照事务不能在这个数据库中进行快照扫描,因为更新事务不能正确生成那些事务感兴趣的快照。 |
PENDING_OFF |
正在禁用快照隔离状态的过程中无法启动新的快照事务。更新事务仍然需要在这个数据库中进行版本控制。现有快照事务仍然可以进行快照扫描。在所有现有事务完成之前,PENDING_OFF 不会变为“关闭”。 |
正在请求快照事务
开发人员和用户必须请求他们的事务可以运行在此快照模式下,以便请求快照的起始点。
语法:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
如果用户在数据库完成这项更改之前执行此会话设置更改,那么用户事务将会失败,并收到错误 3959:数据库 <数据库名> 中的事务失败,因为启用快照隔离的 ALTER DATABASE 命令还没有完成。请等待该命令完成。
理解事务的“开始”
当数据库允许快照时,会对所有更新进行版本控制;但是,事务将要使用的版本依据的是第一个访问数据的语句——而不是 BEGIN TRAN。事实上,如果这个语句不访问数据的话,事务的正式开始甚至不是事务中的第一个语句。
语法:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT BEGIN TRAN SELECT getdate() -- (T1) transaction has not "officially begun" SELECT * FROM <tablename> -- (T2) transaction has officially begun SELECT...-- will see all committed changes as of (T2) SELECT...-- will see all committed changes as of (T2) COMMIT TRAN
结合使用“读取已提交”和“快照隔离”
事务 | 没有基于快照的隔离级别 | 读取已提交快照(非锁定) | 快照隔离 | “读取已提交快照”和“快照隔离” |
BEGIN TRAN |
? |
? |
? |
? |
SELECT * FROM t1 |
锁定(访问时数据被锁定。在执行该语句时,可以看到进行了已提交的更改。读取该资源后即解除锁定。 |
行的已提交版本 – 在该语句开始前提交。非锁定、非阻止。 |
行的已提交版本 – 在事务之前提交。非锁定、非阻止。 |
行的已提交版本 – 在事务开始前提交。非锁定、非阻止。 |
SELECT * FROM t1 WITH (NOLOCK) OR (READUNCOMMITTED) |
可以访问未提交的数据。 |
可以访问未提交的数据。 |
可以访问未提交的数据。 |
可以访问未提交的数据。 |
SELECT * FROM t1 WITH (READCOMMITTED) |
锁定(访问时数据被锁定。在执行该语句时,可以看到进行了已提交的更改。读取该资源后即解除锁定。 |
行的已提交版本 – 在该语句开始前提交。非锁定、非阻止。 |
锁定(访问时数据被锁定。在执行该语句时,可以看到进行了已提交的更改。读取该资源后即解除锁定。 |
行的已提交版本 – 在该语句开始前提交。非锁定、非阻止。 |
SELECT * FROM t1 WITH (REPEATABLEREAD) |
在事务结束之前将一直锁定所访问的数据。其他事务无法修改数据。 |
在事务结束之前将一直锁定所访问的数据。其他事务无法修改数据。 |
在事务结束之前将一直锁定所访问的数据。其他事务无法修改数据。 |
在事务结束之前将一直锁定所访问的数据。其他事务无法修改数据。 |
SELECT * FROM t1 WITH (SERIALIZABLE) |
在事务结束之前将一直锁定所访问的数据集。其他事务无法修改数据或在数据集中添加数据。 |
在事务结束之前将一直锁定所访问的数据集。其他事务无法修改数据或在数据集中添加数据。 |
在事务结束之前将一直锁定所访问的数据集。其他事务无法修改数据或在数据集中添加数据。 |
在事务结束之前将一直锁定所访问的数据集。其他事务无法修改数据或在数据集中添加数据。 |
COMMIT TRAN |
? |
? |
? |
? |
理解行版本控制
版本控制是从“写入复制”(copy-on-write) 机制开始有效启动的,在修改或删除某个行时会调用这个机制。这要求在事务运行时,那些要求较早事务一致性状态的事务必须可以获取行的较旧版本。快照事务可以从这些以前的行版本中有效地“查看”数据的一致性版本。行版本存储在版本存储中,而版本存储则位于 TempDB 数据库中。
具体说来,当表中或索引中的记录被修改时,新的记录会被加上执行修改的事务的“sequence_number”的标记。记录的较旧版本会被复制到版本存储中,新的记录含有一个指向版本存储中的较旧记录的指针。如果存在多个长期运行事务,而且需要多个“版本”,则版本存储中的记录可能会包含指向行的更早版本的指针。特定记录的所有更早版本集中放置在一个链接列表中,对于长期运行快照事务,可能需要对链接进行转换以访问行的事务一致性版本。仅当存在可能需要版本记录的快照查询时,才需要在版本存储中保存版本记录;这种时间长度则主要取决于快照是基于语句或是基于事务。
“读取已提交隔离”中的“行版本控制”
对于运行在“读取已提交隔离”下的选择,如果没有引用行的查询正在运行,那么就不需要特定行版本。换句话说,一旦在修改行的事务之前或事务之中启动的 ALL SELECT 完成后,就不需要特定行版本。在行修改事务之后或之中启动的任何 SELECT 都需要版本存储中的行版本保持活动状态。但是,在所有 SELECT 完成之后,行版本就可以删除了。“读取已提交隔离”下的版本存储不应太大或难以预测,因为它的大小将要由以前行版本的定期验证来自我维护。但是,这取决于语句的长度和复杂性。
快照隔离中的行版本控制
对于运行在快照隔离下的查询来说,在事务结束之前需要保留行版本。由于事务可以跨越多个语句,并且可以持续很长一段时间,因此版本存储可能需要在很长的一段时间内容纳行的多个版本。
在下图中,记录的当前版本是由事务 T3 生成的,它存储在正常的数据页中。而记录的以前版本是由事务 T2 和事务 T1 生成的,它们存储在版本存储?中,因为?仍然有快照事务在访问数据的以前状态。
使用行版本控制将会降低更新性能,因为保存旧有版本要产生额外的工作;但是当存在争用的时候,您会看到由于减少争用而带来的性能提高。此外,快照语句和事务(也叫作版本读取器)在转换版本链接指针时也会产生额外的开销。如果存在很多快照事务,并且是长期运行事务——则可能需要一个更大的 TempDB,并且如果没有正确管理 TempDB,那么性能将会下降。
快照隔离中的 DDL 语句
某些修改对象结构的数据定义语言语句将会被禁止使用,因为通过行版本控制无法看见它们的更改。例如,一个快照事务读取了表 1 并找到了 6 个行:
语法:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT BEGIN TRAN SELECT count(*) FROM <tablename> -- (Returns 6 rows) (
另外一个事务向这个表中添加了多个行——在快照下这对于此事务是不可见的。如果允许此事务可以执行 CREATE INDEX 语句,那么这将如何工作呢?索引是否会建立在数据的快照视图上,或者它会包含所有行——就像通常创建索引那样吗?如果它们选择了第一个,那它们如何使同时发生的 DDL 更改协同工作呢?甚至,如果多个快照创建了其他的索引(而快照事务中禁用 CREATE INDEX),那又会怎样?事实上,多个 DDL 语句是被禁止的,因为它们违反了“快照”的概念,而且必须对实际的基本对象(而不是基本对象的“版本”)调用它们。
快照隔离中不允许使用 DDL 语句
• |
CREATE INDEX |
• |
CREATE XML INDEX |
• |
ALTER INDEX |
• |
ALTER TABLE |
• |
DBCC DBREINDEX |
• |
ALTER PARTITION FUNCTION |
• |
ALTER PARTITION SCHEME |
• |
DROP INDEX |
• |
CLR DDL |
此处没有列出的其他 DDL 语句(例如 CREATE TABLE)是可以使用的,因为其他事务不可能查看数据的以前版本,因为它是一个新对象。这并不违反上面列出的规则。此外,与 Service Broker 一起使用的 DDL 语句总是运行在读取已提交模式下。当这些语句执行时,它们会成功,即使在快照事务中也一样。
快照隔离启动后的 DDL 语句更改
在大多数生产数据库中,架构相对比较稳定。但是,可能需要进行更改。如果系统是高可用系统,并且在架构更改时会同时发生用户活动,那么编程人员就应当对这些更改的错误结果有所准备。由于行版本控制仅存在于数据行中——而不是元数据,一种保持数据库视图一致性的方法就是在快照事务运行时阻止所有服务器实例中的 DDL。这可能会太限制,因为一个长期运行的快照事务可能会在很长一段时间内阻止 DBA 在数据库中执行任何 DDL。在快照事务运行时,DDL 是受支持的,但是如果快照事务尝试访问在其启动之后更改的对象,则它们可能会失败。时间线如下所示(时间从左到右):
T1???|--- 快照事务 ------------------------ 使用对象(失败) -----------|
T2?????????|---DDL,更改对象 ---提交 --|
编程人员应当加入快照事务的重试逻辑,以便处理这类错误,而管理员应当尽量减少在日间的活动高峰期间进行 DDL 更改。
开发最佳做法
一般说来,系统的 DBA 在激活任何一个乐观事务隔离构架之前将会执行例行的系统和应用程序影响检查。这样,开发人员只要了解如何利用新的隔离级别行为来构建更好的应用程序就行了。SQL Server 2005 的新功能为开发人员提供了一个新的工具包;如果您希望产生的应用程序能够按照预期方式工作,那么了解何时以及如何使用这些新工具就非常重要。上述情境介绍了一些新的隔离级别可能会有用的配置;本节将深入介绍开发人员如何使用新的功能。
读取已提交快照
SQL Server 2005 提供了基于非阻止“读取已提交”事务隔离级别的语句级行版本控制;这个选项必须由 DBA 启用,并且不要求利用任何应用程序级更改。如果启用这个选项,则运行在“读取已提交”隔离级别下的事务在它们读取数据时不会保留读取锁定,而是在读取操作执行时使用版本存储来隔离事务与更改。这种保护是语句级的;如果应用程序在相同的读取已提交事务中运行两个选择语句,则结果有可能不同。对于每个执行的语句,“读取已提交快照”将会访问新版本。如果数据更改已经在两个语句之间提交,则两个语句将会看到不同的数据。
这种行为使得开发人员可以消耗更多应用程序数据,而不会随着读取器完成写入器写入的数据而导致阻止增多。常见的语句如下:
SELECT count(*) FROM sales.dbo.orders AS o WITH (READUNCOMMITED)
或者
SELECT count(*) FROM sales.dbo.orders AS o WITH (NOLOCK)
在这些查询中,使用了隔离级别中的一个更改——对于 READ UNCOMMITTED——来停止这个查询对于执行新顺序的阻止,但是看到顺序仍在处理(哪些仍然处于待定状态的事务完成)还是有侧面影响;这可能导致不准确的总计。使用“读取已提价快照”的新行为,查询可以在没有锁定提示的情况下运行,并且不用阻止联机更新器就可以得到一个准确的已提交数据的视图。这在执行一个更为复杂的调用多个集合的查询时更为有利,因为向语句提供了数据库的一个稳定视图。因为一面已被“读取未提交”提示所启用,这避免了由于父记录或子记录的迟到而引起的、结果中很少出现、但仍然可能存在的异常。
在使用新形式的“读取已提交快照”时,一些应用程序——尤其是那些在表中实现队列的应用程序——可能会要求较旧的阻止行为,在这种情况下应当使用锁定提示 READCOMMITTEDLOCK。
SELECT TOP 1 o.OrderID AS [NextOrderID] FROM sales.dbo.orders AS o WITH (READCOMMITTEDLOCK) WHERE o.OrderStatus = 0 -- Unprocessed
在这个查询中,在处理顺序的事务提交或回滚之前,选择语句将会被阻止。这确保了在提交顺序之前,顺序将会被拾取。
如本节前面所述,即使在多步骤事务中使用,新的“读取已提交”行为也只能在语句级起作用。这还有另外一个优点,因为对于“快照”隔离级别来说可能存在的更新冲突对于“读取已提交快照”来说就不会发生,因此,开发人员无需添加额外的逻辑来处理冲突。
在实现一个包含多个选择语句,而这些语句又不要求统一、一致的数据库视图的事务时,应当首先选择“读取已提交”。
快照隔离
有时候,对于一个事务内的多个选择语句使用一个统一、一致的数据库视图很重要。只读应用程序中具有很多这样的示例,例如财务和人力资源报告应用程序,在这样的应用程序中,总计、子总计和总数一定要一致,尽管它们可能是通过几个选择、并且有时是通过几分钟计算出来的。如果系统要求在更长时间内提供一个统一的视图,那么另外一个设计考虑(此处不讨论)将是数据库快照。使用“快照隔离”,您可以确保长期运行事务/报告的一致性,但是如果有一些报告正在运行(没有数据库快照或数据库的静态只读副本)而数据更改了,那么随着细小的差异蔓延到各个报告中,数据质量问题就会出现。
如果要求数据的事务一致性视图,并且 DBA 确定了数据库服务器容量允许轻微的数据库 i/o 增加,那么正在构建可运行多个数据聚合并可针对不断更改的数据库排序多个报告的只读系统(并且需要尽可能最新的数据)的开发人员应当考虑使用“快照隔离”,这种类型的应用程序可以针对主系统构建,也可以针对使用 SQL Server 2005 事务复制构建的副本系统而构建。“快照隔离”可以防止数据编写器(其他用户或复制任务)被报告应用程序所采取的长期运行读取锁定所阻止。
除了要求相同事务的各个查询中数据一致的报告之外,在另外一些情况下开发人员可能也需要使用“快照隔离”。例如,在填充相互关联的数据驱动对话框元素时(还是为了避免各个下拉列表以及其他阵列控件之间的不一致);或者在 DBA 中心的实时系统状态对话框中时(对话框中系统统计数字与数据库中存储的数据有关)。
当应用程序必须针对事务中的数据读取执行更新时,“快照隔离”开发变得有趣起来。这是因为数据库的事务一致性视图,在事务开始的时候,需要屏蔽任何冲突的更新。它们只会在更新被送到数据库中,并且出现一个冲突错误之时才会被发现。
相邻的屏幕截图显示了冲突导致的 SqlException。最佳做法应使应用程序能够截获这个异常,并采取正确的操作。
乐观并发控制的这种侧面影响(应用程序是乐观的,因为没有其他应用程序用户会更新相同数据)意味着开发人员必须额外多做一点工作,以确保其用户的数据不会丢失。很多这样的逻辑已经存在于将它们的数据从数据库中“断开”的系统中,并且可能基于行级增量时间戳值,以提供乐观并发控制。如果您的应用程序中已经存在这种逻辑,那么对于消除在数据填充阶段发生的阻止,以及保持冲突检测而不用在数据库中保持活动事务【它仍是快速进出数据库以避免占用资源(这种情况下即版本存储)的最佳做法】来说,“读取已提交快照”可能是一个很好的选择。
“快照隔离”提供了一个在事务内检测冲突的自动机制,从而避免了添加时间戳列或进行其他架构更改的需要。如果在更新被送到数据库时检测到冲突,系统就会抛出一个 SqlException 并中止当前事务。
请看以下 Visual C# 2005 代码片断(请注意,最佳做法指出 try/catch 逻辑一般情况下应当被事务开始时的(丢失)连接 Open 和 Fill 命令所围绕):
// (Definition of a SqlConnection object skipped) // Define a transaction object using the Snapshot Isolation Level.SqlTransaction DT = sqlCon.BeginTransaction(IsolationLevel.Snapshot); // Hook up Select & Update command handlers to the dataadapter // Use the Snapshot transaction "DT" SqlCommand selectCMD = new SqlCommand(); selectCMD.Connection = sqlCon; selectCMD.Transaction = DT; selectCMD.CommandText = "select MessageNo, MessageText from dbo.DialogText"; sqlDataAdapter1.SelectCommand = selectCMD; SqlCommand updateCMD = new SqlCommand(); updateCMD.Connection = sqlCon; updateCMD.Transaction = DT; updateCMD.CommandText = "update dbo.DialogText set MessageText = @MessageText where MessageNo = @MessageNo"; updateCMD.Parameters.Add("@MessageText", SqlDbType.NVarChar, 15, "MessageText"); updateCMD.Parameters.Add("@MessageNo", SqlDbType.SmallInt, 2, "MessageNo"); sqlDataAdapter1.UpdateCommand = updateCMD; // Now get the data sqlDataAdapter1.Fill(dataSet1, "DialogText");
以上 Visual C# 代码利用 ADO.NET 和 Sql Client,使用由 DBA 启用了“快照隔离”的 SQL Server 2005 的数据来填充数据集。现在,一个普通的 Windows Forms 应用程序将会提交事务,从数据库中断开,然后将数据呈现给用户。在上面的代码中,我们让事务保持打开状态,以便其他应用程序能够更改被读取到数据集 dataset1 中的数据。
以下代码对数据返回数据库进行了说明:
// Bind the data to the form's grid control dataGridView1.DataSource = dataSet1; dataGridView1.DataMember = "DialogText"; dataGridView1.AutoGenerateColumns = true; // ...Time passes, conflicting changes take place // User presses "update now" button:try { sqlDataAdapter1.Update(dataSet1, "DialogText"); dialogTrans.Commit(); dataSet1.AcceptChanges(); } catch (SqlException h) { string errorMessages = ""; for (int i = 0; i < h.Errors.Count; i++) { errorMessages += "Index #" + i + "\n" + "Message:" + h.Errors[i].Message + "\n" + "ErrorNumber:" + h.Errors[i].Number + "\n" + "LineNumber:" + h.Errors[i].LineNumber + "\n" + " Source:" + h.Errors[i].Source + "\n" + "Procedure:" + h.Errors[i].Procedure + "\n"; } if (dialogTrans.Connection != null) { dialogTrans.Rollback(); } MessageBox.Show(errorMessages, "Conflict Errors"); } catch (Exception i) { dialogTrans.Rollback(); }
在第二个代码片断中,数据集对象被绑定到表单中的一个网格控件,在这里用户可以自由对数据进行多次更新——同时其他事务已经产生了冲突的更新。当用户请求让他们的更新存储在数据库中时,数据集的更改是通过 sqlDataAdapter 以一系列数据库更新语句的形式发送的;第一个检测到冲突的更新语句将会引发一个异常,这个异常会回滚工作。如果没有出现任何异常,那么事务就会被 dialogTrans.Commit() 语句显式提交。
上面的异常处理程序会缓存被抛出的 SqlException,并格式化一个错误消息(参见上面的对话框屏幕截图),这个消息会被送到应用程序日志。这个冲突可以作为 SqlException.Errors[i].Number 显式测试,其中 3960 是错误号。最佳做法指出应当对整个错误集合进行检查,以防其他更严重的错误。
同时请注意,SqlException 处理程序会测试 dialogTrans SqlTransaction 对象,以便知道它是否仍旧活动(比如说,它具有一个到数据库的连接)。如果它处于活动状态,则会将其回滚以确保事务一致。如果您尝试提交或回滚不活动的对象,您将会看到一个 SystemException 说明,提示您出现了一个代码为 0xE0434F49 (-532459699) 的 COM+ 异常,并显式文本“This SqlTransaction has completed; it is no longer usable.”。
一旦检测到冲突,应用程序就应当通知用户他们的更改已被抛弃,并为他们提供一个在新事务中重新提交更改的机会。
冲突检测、产生的事务回滚以及随后的重新提交工作的需要说明了开发人员面临的一个决策——如果乐观并发控制机制太过于乐观,数据冲突频繁发生,那么悲观并发控制可能是一个更好的选择。在决定要在应用程序中使用哪种事务隔离方法时,您必须均衡考虑锁定争用引起的阻止以及事务回滚引起的额外工作。
管理最佳做法
作为一个管理员,应慎重考虑是否启用“读取已提交隔离”或快照隔离,因为在使用它们来解决错误问题时,可能会对性能产生负面影响。如果是因为缺乏正确的索引和查询性能负担而导致的性能问题,那么更改行版本控制可能无法解决这个问题。如果由于读取器和编写器的混合工作负载产生大量冲突而导致查询性能负担,则可能需要使用“读取已提交隔离”(以及快照)。如果长期运行事务要求事务一致性,则可能需要快照,但是每次增加都会给 TempDB 带来更加繁重的负载。
数据库级设置
由于快照隔离是在数据库级配置的,因此管理员需要为每个要求使用快照隔离的数据库启用快照隔离。如果尝试对跨数据库的事务使用快照隔离,但又没有把所有数据库都配置为使用快照隔离;那么这个事务就会失败。如果所有数据库都被配置为使用快照隔离,则跨数据库的事务将会在一个服务器实例中使用一个跨多个数据库的一致快照。例如,假如您在同一台服务器中的两个支持快照的数据库中拥有两个表,而您的更新事务要对这两个表进行相同的更改。在快照隔离下,您的事务永远不会给这两个表提供不同的值。
升级问题
尽管升级到 SQL Server 2005 是一个动态的过程,并且仅需要进行内部更改以支持行版本控制;还是需要对所有文本/图像数据进行更改,以便可以进行版本控制。这些更改不是在升级期间进行的,而是在文本/图像数据修改之后才进行。在 SQL Server 2005 中,无论数据库是否配置为使用基于快照的隔离,文本/图像数据都有版本控制更改。对于已升级的数据库,系统将会动态修改文本/图像列,以包含在任何部分的 LOB 数据被更改时的版本控制更改。所有属于该文本/图像值的文本/图像页都将被更改。对于扩展到很多页的较大值来说,这种操作可能非常昂贵(由于页分配、复制和日志记录)。在您修改文本/图像列值时,您将只需要支付这一开销;如果您只是修改父数据行,那么就不会产生任何开销。
由于文本/图像数据修改可以运行在最小日志记录模式下,因此 DBA 应当确定作为升级到 SQL Server 2005 的一部分,单独执行一个人工步骤是否会有好处。当仅对文本/图像值的片断进行很多随机、小型的更新时,碎片大小的更改可能会导致现有程序块中存在很多碎片。尽管程序块的随机、较小的局部更新不是常见类型的文本/图像操作,但在实时系统中这种开销可能会非常昂贵(从时间和日志记录来说)。在 SQL Server 2005 中处于活动状态之前,DBA 需要考虑增加一个步骤(在升级期间),在这个步骤中将所有文本/图像数据更改为这种新格式。
要执行这种修改,一般的步骤是:
1. |
将数据库升级到 SQL Server 2005
有关如何从 SQL Server 2000 成功进行升级的详细信息,请参阅 BOL 中的“准备升级到 SQL Server 2005”主题。 | ||||
2. |
验证和/或更改“恢复模式”为 SIMPLE 或 BULK_LOGGED。首选 SIMPLE,因为在成功完成此过程后将会执行一次完整的数据库备份。 | ||||
3. |
对所有文本/图像数据值执行一次更新 | ||||
4. |
将“恢复模式”更改回所需的恢复模式(比如说“完全恢复模式”)。 |
TempDB 的版本存储用法
版本存储被保存在 TempDB 中。因此,调整 TempDB 的大小对于系统的整体性能以及是否可以对一些长期允许事务使用行版本控制来说至关重要。例如,如果 TempDB 运行的空间太小,性能就会下降,因为版本存储要进行清理。常规清理函数会在后台每分钟执行一次,以便从版本存储中回收所有可重新使用的空间。当 TempDB 空间不足时,系统会在自动增长产生之前调用常规清理函数。当磁盘已满,自动增长不能增加文件大小时,行版本控制就会停止。如果快照查询随后遇到一条记录,并且想要读取这条记录的以前版本,但是由于空间限制没有生成这条记录的以前版本,则这个查询就会失败。更新和删除不会失败,只有请求它们的行版本的查询才会失败,因为一旦版本存储被填满,更新/删除就不会再生成行版本。
一个替代办法就是检测长期运行快照查询/事务,并终止它。通过取消查询,您可以帮助减少版本存储的大小。这可以通过将一个脚本与 TempDB 中的事件关联(错误号 3958)来自动进行。对于大多数应用程序来说,这是更加合理的错误行为。Otherwise, users might have many more transactions that fail due to out of space issues in the version store.
To ensure smooth running of a production system using snapshot isolation, the DBA must allocate enough disk space for TempDB such that there is always roughly 10% free space.当磁盘空间下降到低于 10% 时,系统吞吐量会下降,因为版本清理过程会花费更多时间在回收版本存储中的空间上。
如果 TempDB 中的 IO 性能成为问题,我们建议 DBA 在不同磁盘上创建 TempDB 的多个文件,以提高 IO 带宽。事实上,在多处理器机器上,将文件的数量增加到与处理器的数量相等通常可以产生更大的收益。有关更多信息,请参见“Q328551:Tempdb 数据库的并发增强”。
调整 TempDB 大小
如果仅要求使用“读取已提交快照”,那么调整 TempDB 的大小就没有那么重要,因为行版本不可能保持很长时间。但是当事务非常长时,长期运行事务——读取器和任何编写器——可能会导致问题。此外,如果您正运行在“快照隔离”模式下,TempDB 中的磁盘需求就会增加。建议您使用以下攻势来估算运行“快照隔离”查询时 TempDB 中需要的空间量:
为了估算 TempDB 中需要多少空间,您首先需要考虑让一个活动事务在版本存储中保留它的所有更改,这样随后开始的快照事务才可以获取以前版本。此外,如果存在活动的快照事务,那么快照开始时所有先前的活动事务生成的版本存储数据也必须保留,直到使用它们的最后一个快照事务完成。
[版本存储的大小] = 2 * [每分钟生成的版本存储数据] * [事务的最长运行时间(分钟)]
一个事务每分钟生成的版本存储数据大约等于该事务每分钟生成的日志行。使用“性能监视器”计数器,您可以查看每分钟生成的版本存储数据量。在您的生产系统中,您应当考虑监视这些计数器,以便精确调整 TempDB 的大小。
如果您具有足够的磁盘空间,请总是分配比估算值多的空间,以防止可能的空间问题。在估算 TempDB 的大小时,DBA 还必须考虑 DBCC CHECKDB、DBCC CHECKTABLE、索引构建、查询以及其他活动的空间需求。
监视版本存储活动
可以有很多种方法来监视版本存储活动——从访问虚拟表的函数到性能监视器计数器,再到事件探查器事件。每一种方法都提供了系统中当前发生的活动的不同方面。
函数:dm_tran_active_snapshot_database_transactions ():
这个函数返回一个所有活动事务的虚拟表以及与行版本有关的 sequence_number。只有运行在快照隔离下的事务才包含序列号。自动提交模式下的只读事务和系统事务不会查询在此虚拟表中。
这个函数返回以下列:
列名称 | 类型 | 描述 |
transaction_id |
bigint |
赋予在系统中开始的每个事务的一个唯一编号。每个事务都具有 id。 |
transaction_sequence_number |
bigint |
一个指明事务何时开始的唯一序列号。不生成版本记录、也不使用快照扫描的事务不需要事务 sequence_number。 |
commit_sequence_number |
bigint |
一个指明事务何时结束的序列号(提交或中止)。对于活动事务,这个值为 NULL。 |
is_snapshot |
bit |
事务是否为快照事务 |
Spid |
Int |
启动此事务的连接的进程 id。 |
first_snapshot_sequence_number |
Bigint |
当快照事务启动时,它会拍摄当时所有活动事务的快照。这是快照中 sequence_number 最低的事务。 |
max_version_chain_traversed |
int |
已遍历版本链的最大长度 |
average_version_chain_traversed |
int |
已遍历版本链的平均长度 |
elapsed_time_seconds |
bigint |
自从事务获得 sequence_number 之后的时间(以秒为单位)。 |
表会按顺序输出“transaction_sequence_number”列中的数据。这会显示事务的启动时间以及“"elapsed_time (seconds)”,从而可以帮助您确定哪些事务是长期运行事务。
找到最长(也就是最早)的 10 个事务:
SELECT TOP 10 atx.transaction_id , atx.[name] FROM sys.dm_tran_active_snapshot_database_transactions() AS atx
To find out the transaction that has traversed the longest version chains:
SELECT TOP 1 atx.* FROM sys.dm_tran_active_snapshot_database_transactions() AS atx ORDER BY atx.max_version_chain_traversed
函数:dm_tran_transactions_snapshot():
这个函数返回一个所有活动事务的虚拟表以及与行版本有关的 sequence_number。这个函数返回一个在每个快照启动时所有活动 sequence_number 事务的虚拟表。
这个函数返回以下列:
列名称 | 类型 | 描述 |
transaction_sequence_number |
BIGINT |
事务的 sequence_number,比如说 X |
snapshot_sequence_number |
BIGINT |
事务 X 启动时的活动事务的 sequence_number请注意,第一个“snapshot_sequence_number”也会在 dm_tran_active_transactions 的 first_snapshot_sequence_number 列中显式。 |
Snapshot_id |
BIGINT |
在读取已提交快照下启动的每个语句的语句 Id |
示例:
T1:BEGIN TRAN T1
T1:????SELECT ...FROM ...
T2:BEGIN TRAN T2
T2:????SELECT ...FROM ...
T3:BEGIN TRAN T3
首先,找到事务序列信息:
SELECT atx.transaction_sequence_number , atx.[name] , atx.first_snapshot_sequence_number , atx.commit_sequence_number FROM sys.dm_tran_active_transactions() AS atx
对于 T3,这会返回:
sequence_number | 名称 | first_snapshot_sequence_number | commit_sequence_number |
50 |
T1 |
0 |
NULL |
52 |
T2 |
50 |
NULL |
53 |
T3 |
50 |
NULL |
其次,找到正在运行的快照事务:
SELECT txs.* FROM sys.dm_tran_transactions_snapshot() AS txs
对于 T3,这会返回:
transaction_sequence_number | snapshot_sequence_number | Snapshot_id |
50 |
0 |
0 |
52 |
50 |
0 |
53 |
50 |
0 |
53 |
52 |
0 |
结果显式,有多个快照事务正在运行,其中第一个快照事务的事务序列号为 50。在此事务完成之前,对于此序列号的任何更新都必须保存在版本存储中。事务 T2 使用了一个不同于这个事务的序列号 52。另外一个事务 (T3) 启动了,由于已经存在两个事务并且具有行版本,因此 T3 将使用一个不同于 T1 和 T2 序列号(按照顺序)。
性能监视器计数器
Windows 2003 性能工具(Windows 2000 中的”系统监视器“)使得 DBA 可以在图形界面下监视多个系统和 SQL Server 计数器,可以在性能日志中记录性能计数器,分析性能日志,然后根据这些事件定义操作。其中提供了 API,因此 DBA 可以开发他们自己的程序来访问这些计数器,并采取适当的操作。
不同的计数器如下所示:
计数器 | 说明 |
(1) Free Space in tempdb (KB) |
tempdb 中的可用空间(以 KB 为单位)。 版本存储位于 tempdb 中,因此 DBA 必须确保 tempdb 具有足够的可用空间。这可以通过在 tempdb 中实时运行可用空间计算来实现。 |
(2) Version Store Size(KB) |
版本存储的大小(以 KB 为单位)。 DBA 知道 tempdb 中有多少空间用于版本存储。 |
(3) Version Generation rate(KB/s) |
版本生成速度(以 KB 每秒为单位)。 |
(4) Version Cleanup rate(KB/s) |
版本清理速度(以 KB 每秒为单位)。 |
有了计数器 3 和计数器 4 的信息,DBA 就可以预测 TempDB 中的空间需求,并据以分配空间。
计数器 | 说明 |
(5) Version Store unit count |
版本存储中使用的 AppendOnlyStorageUnit 的编号。这表示当前活动版本单元计数。 |
(6) Version Store unit creation |
版本存储中的新 AppendOnlyStorageUnit 创建。这个计数器表示自从实例启动之后的计数。 |
(7) Version Store unit truncation |
版本存储中的 AppendOnlyStorageUnit 截断。这个计数器表示自从实例启动之后的计数。 |
根据计数器 5、6 和 7,DBA 可以知道版本存储当前使用多少 AppendOnlyStorageUnit 以及有多少 AppendOnlyStorageUnit 被创建和截断。
计数器 | 说明 |
(8) Update conflict ratio |
更新快照事务中具有更新冲突的部分相对于更新快照事务总数的比例。 根据这个比例,DBA 可以知道适当的快照隔离事务级别。我们注意到一个事务可以有多个更新。在这里,比较的基准是进行了更新的事务,而不是更新数本身。之所以不采用更新数作为比较基准,是因为它可能会给出一个容易误导我们的很小的数字。因为对于更新冲突来说,在事务中的其他早期更新回滚时,分子计数仅增加 1;但是对于成功的事务来说,分母计数却以事务中的更新数为增量。 注意:这是一个比例计数器,它提供的是最后一秒的更新冲突。 |
(9) Longest Transaction Running Time |
任一事务的最长运行时间(以秒为单位)。 DBA 可以查看此计数器,并检查是否存在运行时间太长的事务。若要获取更新信息,DBA 可以查询虚拟表 dm_tran_active_transactions() 以获得 transaction_id and spid。按 elapsed_time 列对这个表进行排序也可以找出运行时间最长的 n 个事务。 |
(10) Transactions |
活动事务总数。 这个数字给出系统中活动事务的总数。它包括 SQL Server 的后台内部事务,但不包括系统事务。 |
(11) Snapshot Transactions |
活动快照事务总数。 |
(12) Update Snapshot Transactions |
包括更新语句的活动快照事务总数。 |
(13) NonSnapshot Version Transactions |
生成版本记录的活动非快照事务总数。 这个数字是从不请求 SNAPSHOT ISOLATION 的更新中得来的。 |
由于所有进行更新的快照事务到会导致版本生成,因此导致版本生成的事务总数是计数器 12 和 13 的总和。同时根据计数器 11 和 12,DBA 可以计算出只读快照事务的数量。
因此,根据这些计数器,DBA 就可以知道使用了多大程度的版本控制功能以及如何使用它。
上述所有计数器都是服务器端计数器,它们都被组织到一个新的“性能监视器对象”中,这个对象被称为“SQLServer:Transactions”。
更多相关信息
由于快照隔离会影响到系统的管理和开发等方面,因此请务必确保了解这些方面。如果存在长期运行事务但修改是不变的,则 DBA 不必允许快照隔离;如果 TempDB 的大小设置不当,用户在提交更改时可能会遇到问题。此外,如果开发人员希望设置“读取已提交快照”,而不是在发生数据不一致的时候没有检测到。请务必阅读测试新闻组中的所有相关资源和参与方,以获得更多信息。
联机丛书主题
了解快照隔离
调整事务隔离级别
使用快照
准备升级到 SQL Server 2005
有关的知识库文章
Q328551: Tempdb 数据库的并发增强。
附加读物
归纳的隔离级别定义:http://research.microsoft.com/~adya/pubs/icde00.pdf?
ANSI SQL 隔离级别的评论:http://research.microsoft.com/research/pubs/view.aspx?tr_id=5