开发人员升级至ASE 15.0的10大理由(八)
2011-05-08赛贝斯软件有限公司
9 极端事务处理(语句缓存、延迟提交、tempdb)
随着近几年混合负载/实时报表的需求推动,极端事务处理(XTP)被越来越多地需要。它已在第5部分的一开始即通过OLTP增长的曲线而展示(可配置的DSS查询优化)。
XTP与OLTP是不同的,正如OLTP也与DSS不同。当然,一些系统需要支持XTP和OLTP—颇像“混合负载”系统支持了OLTP和有限的DSS需求。XTP和OLTP的一些不同之处是OLTP主要关注的是高并发量的短小查询和小事务,而XTP则关注更少的并发量,但关注趋近于连续的数据插入和实时的查询结果,要么是高度并发的,要么是持续时间短的—通常都在非常的数据流上。例如,OLTP会有数百个用户,查询响应时间为1 s或低于1 s可能是能被轻松接受的访问的数据是数天、数月甚至是数年之久的。在XTP系统中,查询需要小于10 ms范围的响应时间通常针对仅仅数秒前的数据。类似地,数据持久化是OLTP中ACID的重要考虑,在需要的数据状态达到前,XTP中的数据持久化可能不被考虑例如预订了的交易订单。因为无需或推迟了持久化在失败后的事务恢复需求也大大放松了。
因此正如在相同的系统上不同的应用程序需要考虑使用不同的应用程序情形:allrows_oltp、allrows_mix或allrows_dss,非常高事务处理的应用程序需要考虑启动ASE 15.0中的一些新特性来取得优势。另外,在一些并非如此极端处理需求的应用程序中可能也会受益,如果原先的应用程序情形是OLTP主导的。例如,Sybase自己的复制服务器产品就因利用了这些特性而表现出吞吐量提升(文字自动参数化语句缓存和延迟提交)。
考虑到极端事务处理的需求,ASE利用了其中的几个特性来专门解决在当今的ASE应用程序中成为妨碍高容量OLTP的瓶颈。这些瓶颈包括:
(1)与执行时间比较,对简单查询和DML语句的额外优化开销。
(2)等待完成先记录日志的时间拖延,以便保证事务持久性
(3)应用程序在临时数据库工作表上的额外物理IO。
9.1 语句缓存和文字参数化
正如以前提及的,查询的优化时间可能大大超过其执行时间。虽然可以在复杂查询中得以验证但甚至在简单查询和大部分DML操作时都是如此。例如,对一张包含普通索引的表进行单一插入,根据索引的数量,通常仅需要25~30的逻辑IO和3~5物理写入。如果将写入缓存—如果不包含事务日志—则整个插入将完全在内存中。内存操作仅需纳秒级别(即使是物理IO的服务时间也可在2~6 ms范围内完成),所以单一插入在最坏的情况下也可能在几毫秒内。然而优化需求(例如,分区消除、决定聚集索引或堆等)可能会超出很多。通过利用文字参数化的语句缓存,插入SQL命令的处理将会极大降低分析所带来的额外开销。
和存储过程一样,必须注意缓存的语句,因为如果文字值改变了,缓存的查询计划可能并非优化的。例如,考虑常见的数据区间不同(1个星期与1年比较)与存储过程优化的问题…文字参数化的语句缓存也会遇到同样的问题因此,可能需要保证语句缓存进针对更可预测的OLTP情况启动,可使用之前展示的登录触发器方法。像复制服务器(Replication Server)这样的应用程序可明确地受益,因为它是DML密集的。
9.2 延迟提交
在消除了优化的问题后,下一个主要的瓶颈就是事务日志。多年前,日志竞争通过用户日志缓存(User Log Cache)的实现而缓解了。在大多数XTP应用程序中,一小部分的电子传输驱动了常常与大量用户并发读取捆绑的DML需求。因此,事务日志的竞争并非大问题—实际上数据持久化并非要素,所以整个记录事务的概念就无需额外开销了。使用先写入日志的问题是每个语句必须等待物理IO才能被刷新至磁盘。这对使用小批量的原子插入来降低网络时间的情况是成立的。考虑以下序列:insert into table (
- ) insert into table (
- ) insert into table(
- ) insert into table (
- ) insert into table (
- ) insert into table (
- ) go
无论它使用脚本显式创建的—还是通过java.sql.Statement的addBatch()方法创建的,每个插入都代表了一个原子事务。结果就是,在下一个插入发生前,它必须等待上一个插入的提交—也就意味着必须等待最后一个日志页面被刷新至磁盘。
ASE 15.0中新增了延迟提交数据库/会话选项以规避该问题。支持事务在ULC刷新至日志缓存完成后即可提交,即在最后一个日志页被物理刷新至磁盘前。注意它指的仅是最后一个日志页面—所以在典型的ASE中,页面大小为2 KB,ULC为4 KB,充满的ULC仍必须等待至少一个日志页被刷新—而非两个。因此,该选项对完全能被一个单独的日志页面容纳的事务最为有效—虽然较长的事务也可能小程度受益。
9.2 .1 原子插入测试
为了展示影响,请查看下表:
create table batch_inserts (
row_id varchar(45) not null,
logical_server varchar(30) not null,
client_host varchar(30) not null,
thread _num smallint not null,
thread_row int not null,
client _timestamp datetime not null,
column _06 int not null,
column _07 float not null,
column _08 int not null,
column _09 float not null,
column _10 int not null,
column_11 int not null,
column _12 int not null,
column _13 int not null,
column _14 char(10) not null,
test_string varchar(80) null,
constraint batch _inserts _PK primary key (row_id)
)
lock datarows
go
create index host_thread_idx
on batch_inserts (client_host, thread_num, thread_row)
go
create index server_time_idx
on batch_inserts (logical_server,client _timestamp)
go
grant all on batch_inserts to public go
使用该模式,我们将从一个运行了JDBC驱动程序的客户端机器上运行两组测试:
原子插入—10个客户端线程使用单独的查询语句(非批量)来执行1万次原子插入。
50批量插入—10个客户端线程将用50个查询为一批来执行1万行插入(例如,客户端将发送200批SQL,每批包含50个插入语句)。
每组测试将包含6个查询流格式:
(1)标准插入语句命令
(2)启动了语句缓存
(3)启动了延迟提交
(4)启动了语句缓存和延迟提交
(5)动态SQL (完全预备语句)
(6)动态SQL且启动了延迟提交
由于语句缓存提供了动态创建的编译语句,在其上测试动态SQL就没有意义了。请查看两组测试的结果,见图1。
图15 语句缓存与延迟提交对吞吐量的影响(启动了DIRECTIO的设备)
对简单高速插入中,结果明确显示将事务日志刷新至磁盘是最大的瓶颈。该图中的其他异常可以轻松被解释。因为动态SQL使用TDSRPC接口,而非TDSLANG,它一次仅能提交一个单独的准备语句。因此,当使用诸如JDBC的addBatch()和execute-Batch()之类的批量方法时,批必须被分解并序列化发送—结果比使用executeUpdate()单独发送每个语句还稍慢。另外,当执行批量SQL语句时,在语句缓存中找到语句的过程比单独的语句更为有效。
最明显的问题就是如何与ASE 12.5.4比较。与上述相同的基准在缓存文件系统设备上运行(与DIRECTIO相对)—它通常将消除大部分延迟提交的效果,因为日志写入被缓存了-但也仍可考虑性能上的差异,见图16。
图16 ASE 12.5.4与ASE 15.0.3比较(相同配置-4引擎和6GB内存/缓存文件系统设备)
总体而言,可以看到ASE 15.0.3比12.5快10%左右—ASE 15.0的高性能特性几乎将使用语言语句的已有应用程序吞吐量提升了一倍。注意这个测试是在一个4引擎的ASE 15.0.3,运行于两个4核XEON处理器、SATA-RAID磁盘的Red Hat Enterprise Linux 5.1上较低端的机器结果却能给人更深的印象。
9.2.2 小批量结果
对称为“小批量”的技术术语引用频繁贯穿了本文。小批量依赖于使用批量数据接口至ASE中和小批量大小。在ASE 12.0中,Sybase新增了数组插入特性,它支持一组数据使用类似于bcp的批量方法插入。如果有兴趣的话,它参照到了服务器配置“cis bulk insert array size”—先前在积极聚合和重定向连接中的分布式查询调优部分提及。数组插入特性的操作与使用-B选项的慢速bcp很像,它要完全记录日志并完全事务化。但是,与普通的语句不同,它无需分析、编译和优化—仅是高速加载。
为了保证应用程序延迟和响应时间最小,小批量的行在一次插入。通常,批次要么按照计时基准(例如每250 ms)被刷新,或在批次的大小达到预定限制后。另外,为了处理单一来源的数据量或同时处理并行来源,多并发线程被用来在同一表中加载。
当使用Java/JDBC时,在满足以下条件时,数组插入被启动:
(1)连接属性ENABLE_BULK_LOAD被配置为“true”(需要SDK 15.0 ESD #10及更高版本)。
(2)预备语句使用java.sql.Connection.prepare-Statement()方法,通过JDBC调用反复执行的DML操作必须规范。
(3)preparedStatement.addBatch()方法用来构建未决插入的小批次,然后通过preparedStatement.executeBatch()方法来提交。
因为正在讨论事务日志的影响,完全记录日志的批方法也能受益于延迟提交特性。考虑使用刚才的基准使用10个并发小批量线程插入至未分区表中的测试结果,见图17。
图1710 个线程每个插入5万行数据
正如所展示的结果,甚至在使用延迟提交的动态SQL情况下,小批量提供了巨大的吞吐量提升。但是,看起来延迟提交对小批量基本没有帮助,因为两条线几近重合。真正的原因是:
在使用小批量时,瓶颈并非事务日志—而是网络。可使用ASE MDA监控表来查看单独的进程在等待什么,见图18。
图18 测试中显示出网络为主要瓶颈的MDA样本
如果加载过程与数据服务器运行在同一主机或快于1 Gbs的网络,或许网络影响可以减少或消除。在该情况下,可能使用延迟提交时会受益。通过关闭了延迟提交的动态SQL运行来比较,见图19。
多一点解释,因为使用的是行级锁定和隔离级别1,在表级别上对严格插入没有竞争。因此,150号事件(等待锁)是等待日志标志/自旋锁。
9.2.3 数据丢失风险
开发人员和DBA在考虑启动该选项时会关心是否对数据库一致性造成影响。答案是不会。在最坏的情况下可能发生的是最后一个数据页丢失。损失多大呢?答案是一个单独的页面中能包含多少数据可能仅仅是在崩溃前的几微 秒上插入的2~3行数据。对于大部分运行于该速度下的系统来说,因为系统崩溃而损失2~3行数据不是问题。如果该应用程序被构建成处理HA失败转移的,很有可能最后一批仍旧在缓存,只需简单地重新提交数据而不用担心数据丢失。通过缓存之前的批次,完整的可恢复性也可得到保证。Sybase复制服务器(Replication Server)通过对每个连接的“保存间隔”提供该功能 —根据ASE失败后的恢复,可能设置几秒就足以保证数据零丢失。
9.3 tempdb优化
ASE 15.0引入了一些tempdb的提升,消除了在ASE 12.5.4中的一些瓶颈和一些细小的繁琐,例如:
(1)#临时表名现在可用238个字符来区别而非12个-消除了试图创建含糊的#临时表名来避免重复表错误的需求。
(2)既写入到用户数据库又写入到tempdb的事务会在每次上下文改变时造成ULC刷新(写入用户db;写入tempdb;写入用户db→两次ULC刷新)。现在它已通过使用单独会话tempdb日志缓存而消除了。
(3)影响OAM页面的操作—例如创建新表或扩展缺省空间都会导致ULC刷新以记录OAM页面的修改。现在它作为系统日志记录而不再需要ULC刷新了。
(4)实现了行级目录 - 去除了对系统表的竞争。
(5)增强了tempdb的消极I/O实现-尤其对tempdb日志—在tempdb缓存足够大的情况下真正解决了物理I/O。
虽然消极I/O肯定对tempdb的日志操作有帮助,甚至tempdb中最小的记录日志批操作也受益于tempdb的优化。考虑以下查询的结果,见图20。
declare @now datetime
select @now=getdate ()
-- 1,350,255 rows in salesdetail select * into #foo from salesdetail select
ms=datediff (ms, @now,getdate ()) go
图20 比较批量插入1 350 255行数据至tempdb的时间
正如所展示的,ASE 15.0.3比较ASE 12.5.4来说使用select/into影响超过一百万行快了近1 s。
对开发人员来说增加消极IO的一个有趣的方面是,它支持创建的临时数据库能提供几近内存数据库的功能。可通过以下实现:
(1)增加数个(例如,5~6个)数据缓存UFS设备和3~4个日志缓存UFS设备确保DIRECTIO和DSYNCH都失效。设备总大小应与临时数据库指定能使用的总内存大小相同。
(2)在设备上创建临时数据库作为用户tempdb,跨设备用轮循调度的方式使用一系列小的分配(例如,66 MB)。
(3)创建与数据库大小一致的单独命名缓存。缓存必须是混合的—为数据和日志服务—并将用户tempdb与缓存绑定。
(4)通过将服务器日志文件的缓存状态修改为“HK Ignore”或通过SQL语句“update master.dbo.sysconfigures set status = status | 16 where parent=19 and name=‘
(5)将tempdb会话日志缓存大小配置成较大的数值(如 128 KB)
(6)将用户CPU和I/O计数关闭
以上帮助创建能提供更高性能的应用程序特定tempdb—如果应用程序与该tempdb绑定。