概述
为了更好地诊断性能问题,本文讨论了一个有条理的过程,帮助确定数据库是否存在性能问题,并制定补救措施。当 DB2或电子商务应用程序的性能未达到预期目标时,整个组织和财务底线都可能受到影响。
本文旨在指导 DBA 在 Windows上定义和诊断 DB2 的性能问题。本文涵盖的内容如下:
- 使用 Windows 工具收集的信息
- 使用 DB2 工具收集的信息
- 问题的调查和解决
假设
本文假定读者对数据库、SQL 和 DB2 LUW 已有基本的了解。对 UNIX的性能调优和监控有基本的了解可能会对您有所帮助。
性能问题以及它们与系统资源的关联
性能问题涵盖广泛的场景:
- SQL 查询执行速度比预期慢
- 工作负载或批处理作业没有在预期时间内完成,或事务率和吞吐量在一段时间内逐步下降
- 系统整体速度下降
在大多数情况下,出现性能问题是因为系统资源的使用不当或 CPU、IO 和内存等资源的过度使用,这往往揭示了这些系统资源中的瓶颈。在经过适当调优的环境中,系统资源会得到***使用,不会过度依赖其中任何一种资源。
诊断性能问题的***个步骤是,识别所有资源瓶颈。Windows 提供了一些可以帮助识别这些瓶颈的工具。
CPU 瓶颈
如果系统上有一个或多个 CPU 一直显示 90% 以上的利用率,这通常意味着系统存在 CPU 瓶颈。任务管理器可以帮助您找出系统是否存在 CPU 瓶颈。其他工具(如 perfmon.exe 和资源监视器)会显示 CPU 利用率,也可以帮助识别 CPU 瓶颈。
内存瓶颈
内存瓶颈并不是很常见,这主要是因为数据库的堆和参数通常是根据可用内存进行配置的。但是,如果在 perfmon 和资源监视器中看到非常低的可用内存,那么这可能表示存在内存瓶颈。有时候,在使用 STMM 时,系统上的可用内存可能会非常低,但这并不总是意味着该系统存在内存瓶颈。
网络瓶颈
如果在资源监视器中看到非常高的网络利用率,那么这可能表示存在网络瓶颈。资源监视器以百分比的形式显示网络利用率,这有助于快速识别网络瓶颈。如果资源监视器显示网络利用率在 80% 以上,这通常表示存在网络瓶颈。
I/O 瓶颈
如果系统上有一个或多个磁盘在 90% 以上的时间一直处于忙碌状态,或磁盘队列长度不断显示较高的数量,这通常意味着系统存在 I/O 磁盘瓶颈。Windows工具(如资源监视器和 perfmon)可以帮助识别 I/O 瓶颈。任务管理器确实可以显示 I/O 活动,但资源监视器和 perfmon 可以显示每个磁盘的 I/O 详细信息,还可以显示活动时间的百分比,这有助于识别任何特定的磁盘上是否存在瓶颈。
有多种 Windows 工具可以帮助确定系统是否有一个或多个资源瓶颈。#p#
步骤 1:使用 Window 工具识别瓶颈
任务管理器
任务管理器是获得有关整个系统的使用情况的信息的最快方式。例如,图 2 是任务管理器的 Processes 选项卡的屏幕截图,其中的列提供每个进程的 CPU、内存、I/O 统计信息 (View > Select Columns)。任务管理器很好地总结了 CPU、I/O、内存和网络利用率。任务管理器也提供了进程的详细信息,帮助找出哪些进程正在消耗最多的 CPU,哪些进程正在执行最多的 I/O 等。
如果任务管理器显示,整体 CPU 利用率一直超过 90%,那么这是存在 CPU 瓶颈的一种迹象。任务管理器也在 Performance 选项卡显示每个 CPU 活动。如果其中任何一个 CPU 的利用率一直接近 100%,这可能也意味着存在一个 CPU 瓶颈。通常情况下,这意味着数据库中的工作负载是单线程的,无法利用系统上的所有可用 CPU。图 1 是单线程应用程序运行的一个示例。即使只有一个 CPU 处于忙碌状态,没有使用其他 CPU,它仍然是一个 CPU 瓶颈。
任务管理器还显示每一个进程从磁盘上读出/写入的数据量的详细信息。该信息本身非常有用,但它没有显示每个磁盘的利用率百分比。这使得用户很难仅通过任务管理器来断定系统是否存在 I/O 瓶颈。
图 1. 任务管理器 — 单线程工作负载示例
图 2. 任务管理器显示了内存使用率***的进程
资源监视器
资源监视器是在 Windows 2008 和 Windows 7 上提供的另一个 Windows 工具。它提供有关 I/O、CPU、内存和网络使用情况的详细信息。该工具显示了在您的系统上运行的所有进程的实时信息,并提供了根据用户要求筛选数据的能力。可以基于内存、CPU、磁盘和网络使用情况做到这一点。Overview 选项卡显示了整个系统的活动,并提供了系统中瓶颈的快照。I/O 和网络使用情况以可用带宽的利用率百分比的形式显示。这有助于识别系统是否存在 I/O 瓶颈或网络瓶颈,从任务管理器中不可能确认这一点。此外,资源监视器显示了每个磁盘的磁盘队列长度,对于确定磁盘是否有足够的带宽来解决系统的 I/O 需求而言,这很有用。
图 3. 资源监视器
图 4. 资源监视器 DB2 磁盘活动
perfmon
虽然任务管理器和资源监视器对于确定系统活动都是很好用的工具,但不能使用它们将系统活动记录在日志中,以供日后分析。Perfmon 工具可以将系统活动记录在日志文件中。这提供了灵活性,让管理员和 DBA 可以在一天中的不同时间收集 perfmon 数据,并在以后使用它们进行分析。Windows 附带的 perfmon 工具可用于捕获性能数据和资源使用情况的统计数据。对于许多类型的问题调查,了解如何设置和捕获 perfmon 日志都很关键。在监视 I/O 时需要注意的是:需要通过运行 diskperf -y (-ye 表示带区集)启用磁盘计数器,然后重新启动。在 Windows 2008 或 Windows 7 上,需要运行 perfmon,将活动捕获到日志文件中:
- 在命令提示符下运行 perfmon。
- 从左面的框架中选中 Performance Monitor。
- 右键单击它,并选择 New > Data Collector set。创建一个合适的名称,并单击 Next。
- 提供一个将会保存日志的目录名称。
- Data Collector set 出现在左边的框架中。在左边的框架选中 Data Collector Set > User Defined,并选中您在步骤 4 所选择的名称。它的状态应该是已停止,因为我们希望在收集开始之前,先添加所需的计数器。
- 右键单击已定义的 Data collector set 并选择 New > Data Collector。提供一个名称,并选中 Performance counter data collector,然后单击 Next。选择采样频率并增加性能计数器。Perfmon 工具提供了很多计数器来监视多种参数,下面介绍最有用的几个计数器。这对于收集数据是一个很好的出发点。根据具体的要求和情况,用户可以收集和监视其他计数器。
7.一旦完成了如下图所示的性能计数器选择,就可以收集所需的时间间隔的数据。可以使用所选择的数据收集器集的属性部分对此进行修改。为所选择的计数器收集的数据可以保存为表或电子表格的格式。在 File 选项卡下的属性部分,可以指定输出的格式。
图 5. perfmon 性能计数器
8.一旦准备好进行数据收集,就可以启动您的工作负载或查询,立即通过单击选定的数据收集器集上的 Start 开始数据收集。
完成查询/工作负载后,停止监视数据收集,并检查收集到的数据。#p#
选择正确的诊断工具
对于普通的监视,perfmon 是一个很好用的工具。还可以保存其日志,以便更轻松地比较系统在按预期工作时和系统有性能问题时的系统活动。这往往可以为手头上的问题提供有价值的线索。然而,快速查看任务管理器和资源监视器的数据有时也可以帮助实时查找系统中的瓶颈。一旦确定了瓶颈,就可以采取相应的措施来消除瓶颈。
步骤 2:I/O 瓶颈 — 详细研究
如果 perfmon 显示有一个或多个磁盘的磁盘时间在 80% 以上,或资源监视器显示有一个或多个磁盘上的活动时间在 80% 以上,那么这通常意味着系统中存在一个 I/O 瓶颈。可以从 perfmon 或资源监视器确定具有很高利用率的一个或多个磁盘。一旦确定了大量使用的磁盘,就可以找出放置在磁盘上的内容。
是否有任何 DB2 表空间容器放置在磁盘上?
db2 list tablespace containers for <tbsid> |
对数据库中的所有表空间重复此命令。
或者,DB2 日志文件是否被放置在大量使用的磁盘上?
db2 get db cfg for <dbname> |
搜索 newlogpath 数据库配置参数。
或者,这些磁盘是否包含实用程序文件,比如备份目标或加载文件?查看已执行的备份/负载命令。根据大量使用的磁盘上的内容,解决方案也会有所不同。
表空间容器上的磁盘瓶颈
如果将大量使用的磁盘分配到表空间容器,那么请找出表空间中的对象。如果表空间对应于某个数据表空间,那么请找出在表空间中创建的表。
db2 select tabname from syscat.tables where tbspaceid = <hot tablespace id> |
从 MON_GET_TABLE 表函数中找出最活跃的表。下面的查询将列出具有最多行读取的表。
清单 1. 表空间中最活跃的表
- db2 "select varchar(tabschema,20) as tabschema, varchar(tabname,20) as tabname,
- table_scans, sum(rows_read) as total_rows_read,
- sum(rows_inserted) as total_rows_inserted,
- sum(rows_updated) as total_rows_updated,
- sum(rows_deleted) as total_rows_deleted
- FROM TABLE(MON_GET_TABLE('','',-2)) AS t
- WHERE TBSP_ID = hot tablespace id
- GROUP BY tabschema, tabname
- ORDER BY total_rows_read DESC"
注意:MON_GET_TABLE 函数提供了许多有用的信息。它在表上跟踪表扫描的数量。如果表扫描较多,那么可能意味着该表没有适当的索引,或者查询没有使用表上现有的索引。
一旦确定了活跃的表,就可以使用 MON_GET_PKG_CACHE_STMT 表函数轻松找出表上执行的 SQL 语句。
清单 2. 找出给定表上的查询
- db2 "select section_type, executable_id, package_name,num_executions,
- char(stmt_text, 100)
- from table (MON_GET_PKG_CACHE_STMT ( 'D', NULL, NULL, -2)) as T
- where stmt_text like '%hot table name%' "
通过这种方式,很容易找出活跃的表上导致大量读取/写入操作的 SQL 语句。使用 Design Advisor 确定 SQL 语句是否使用了表上正确的索引。如果表中有适当的指标,但没有用到这些指标,那么请检查表上的统计信息是否是***信息。不正确或过时的统计信息可能会导致优化器选择一个次优的访问计划。
临时表空间上的磁盘瓶颈
如果将大量使用的磁盘分配到临时表空间,这表示数据库上有大量排序活动。高水平的临时表空间 I/O 活动往往与结果集较大或有大量排序的大查询一起出现。在这种情况下,需要确定是否有大量排序溢出到磁盘。 MON_GET_PKG_CACHE_STMT 可以提供造成排序溢出的 SQL 语句。
清单 3. 有大量排序活动的查询
- db2 "select section_type, package_name,num_executions, total_sorts, sort_overflows,
- char(stmt_text, 100)
- from table (MON_GET_PKG_CACHE_STMT ( 'D', NULL, NULL, -2)) as T
- where sort_overflows > 1
- order by sort_overflows desc"
如果有太多排序溢出,那么请检查 SORTHEAP 或 SHEAPTHRES 配置参数是否被设置为较低的值。如果排序的相关参数得到正确设置,那么请确定是否有可能通过创建索引来避免出现大量排序。对排序量较大的查询使用 Design Advisor,查看是否能够通过创建额外的索引来避免或减少排序。
事务日志上的磁盘瓶颈
在 OLTP 环境中,事务日志的性能是非常敏感的。MON_GET_TRANSACTION_LOG 表函数提供了有关事务日志的详细活动。
清单 4. 事务日志活动
- db2 "select log_reads, log_read_time, log_write, log_write_time, num_log_buffer_full,
- num_log_data_found_in_buffer
- from table(MON_GET_TRANSACTION_LOG(-1)) as T "
num_log_buffer_full 显示了在将新的日志写入缓冲区之前,日志缓冲区被充满并且必须刷新到磁盘的次数。如果这个数字在一段时间内一直在增加,则意味着日志缓冲区对于数据库上的工作负载而言过小。增大 LOGBUFSZ 数据库参数值有助于提高日志性能,并减少事务日志磁盘上的 I/O。
num_log_data_found_in_buffer 显示了代理从缓冲区中读取日志数据的次数。从缓冲区中读取日志数据优于从磁盘读取日志数据,因为后者较慢。可以结合使用此元素和 num_log_read_io element,以确定是否需要进一步增大 LOGBUFSZ 数据库配置参数。
步骤 3:CPU 瓶颈 — 详细研究
如果 perfmon 或资源监视器显示有一个或多个 CPU 的使用率超过 90%,那么这通常意味着系统存在 CPU 瓶颈。与 I/O 瓶颈一样,***个步骤是识别消耗大量 CPU 的数据库操作。通常情况下,已知道有一些数据库操作会消耗大量的 CPU:
- 语句编译
- LOAD、BACKUP、runstats 等实用工具
- 大量排序活动
要确定在查询编译中是否花费了大量 CPU,请查询 MON_GET_WORKLOAD 表函数。
清单 5. 在不同活动中花费的 CPU 时间
- db2 "select varchar(workload_name,30) as workload_name,
- sum(total_cpu_time),sum(total_compile_proc_time),
- sum(act_rqsts_total), sum(total_compilations),
- sum(total_act_time), sum(pkg_cache_inserts),
- sum(pkg_cache_lookups)
- from TABLE(MON_GET_WORKLOAD('',-2)) as T
- group by workload_name"
如果 compile_proc_time 高于 5-10% 的 total_cpu_time,并且 pkg_cache_inserts/pkg_cache_lookups 高于 4-5%,则数据库在语句编译上花费了太多的时间。这可能是因为查询被重复编译,或者是因为包缓存过小,查询必须迁离到其他地方,以便为新的查询腾出空间。如果应用程序使用字符串文本而不是参数标记符,则会重复编译 SQL 语句。在 DB2 9.7 和 10.1 中,DB2 提供了一个数据库配置参数 STMT_CONC(语句集中器)。当启用该参数时,语句集中器会修改动态语句,以便允许共享更多的包缓存条目,并减少语句编译。
要找出造成高 CPU 利用率的所有实用工具,请查询 MON_GET_WORKLOAD 表函数。返回的指标展示了用于已提交的请求的所有指标的汇总,这些请求是由映射到已识别工作负载对象的连接提交的。在请求的执行过程中,指标在工作单元边界上或定期被汇总到一个工作负载。由该表函数报告的值,反映系统在最近一次汇总时的当前状态。指标是严格递增的值。为了确定在某时间间隔花费在 LOAD 和 reorg 等实用工具中的时间,可以使用 MON_GET_WORKLOAD 查询在时间间隔的开始和结束时使用的指标,并计算其差异。
清单 6. 在 LOAD 和 runstat 实用工具中花费的 CPU 时间
- db2 "select varchar(workload_name,30) as workload_name,
- sum(total_loads), sum(total_load_proc_time),
- sum(total_runstats), sum(total_runstats_proc_time)
- from TABLE(MON_GET_WORKLOAD('',-2)) as T
- group by workload_name"
LOAD 等实用工具旨在充分利用可用资源,***限度地提高性能。如果这些实用工具占用了比预期多的 CPU,那么可以通过设置 util_impact_limit 数据库配置参数对实用程序进行节流。另一个占用大量 CPU 的数据库操作是 sort。正如在 I/O 瓶颈部分所介绍的,找出执行最多排序活动的查询,并使用 Design Advisor 确定是否可通过创建额外的索引来避免排序。
步骤 4:内存瓶颈
内存瓶颈并不是很常见,主要是因为数据库的堆和参数可以根据可用内存进行设置。大多数 DB2?? 堆是自动的,并基于可用内存提供分配值。STMM 在利用可用内存和将内存分配给最需要内存的堆这两方面做得很好。但是,在不使用 STMM 的情况下,有可能存在内存使用不当的情况,如果内存分配得过多(也就是说,分配值高于可用内存),则有可能导致大量分页活动。如果 Perfmon 或资源监视器显示了许多分页活动,这通常是因为分配给不同堆的内存已超过实际内存。在这种情况下,***是打开 STMM,让 DB2 调优缓冲池、排序堆和其他堆的内存。
步骤 5:网络瓶颈
出现网络瓶颈的原因通常是存在大量四处移动的数据(比如非常大的结果集和客户端负载等),或者操纵 LOB 的应用程序位于客户端-服务器架构中。MON_DB_SUMMARY 管理视图很好地说明了等待不同的资源所花费的时间。NETWORK_WAIT_TIME_PERCENT 字段提供了等待网络响应的时间百分比。通常情况下,等待网络所花费的时间应该小于 1%。如果该值高出几个百分点,并且 perfmon 和资源监视器显示网络带宽在大量被占用,那么系统可能遇到了网络瓶颈。在这种情况下,应用程序可以将一些应用程序逻辑以存储过程或者 UDF 的形式移动到服务器。在客户端负载的情况下,可以将负载拆分为更小的部分,在不同的时间执行它们,而不是一次全部加载它们,这样做可以减少网络流量。
步骤 6:锁定问题
如果系统没有任何资源瓶颈,但性能仍然较差,这可能是因为锁定问题。MON_DB_SUMMARY 管理视图中的 LOCK_WAIT_TIME_ PERCENT 字段提供一个高层次的视图,说明了在数据库级别的锁等待上花费的时间。为了获得在锁等待中花费了时间的工作负载的详细视图,请查询 MON_GET_WORKLOAD 监视器表函数。
清单 7. 在锁等待中花费的时间
- db2 "SELECT varchar(workload_name,30) as workload_name,
- sum(lock_wait_time) as total_lock_wait_time,
- sum(lock_waits) as total_lock_waits,
- sum(lock_timeouts) as total_lock_timeouts,
- sum(lock_escals) as total_lock_escals
- FROM TABLE(MON_GET_WORKLOAD('',-2)) AS t
- GROUP BY workload_name
- ORDER BY total_lock_wait_time DESC"
#p#
步骤 7:调优页面清理活动
除了检查系统资源瓶颈和锁定问题,在所有数据库环境中还有另一些重要的事项需要注意。页面清理和预取是两项重要活动,需要对它们进行适当调优来获得***性能。在某些情况下,如果页面清理没有得到正确的调优,则有可能出现 I/O 瓶颈。监视表 MON_GET_BUFFERPOOL 提供了一些找出页面清理和预取活动的指标。
清单 8. 页面清理活动
- db2 "WITH BPMETRICS AS (
- SELECT bp_name,
- pool_data_writes, pool_async_data_writes,
- pool_index_writes, pool_async_index_writes,
- pool_no_victim_buffer, pool_lsn_gap_clns,
- pool_drty_pg_steal_clns, pool_drty_pg_thrsh_clns
- FROM TABLE(MON_GET_BUFFERPOOL('',-2)) AS METRICS)
- SELECT
- VARCHAR(bp_name,20) AS bp_name,
- pool_data_writes, pool_async_data_writes,
- CASE WHEN pool_data_writes > 0
- THEN DEC((FLOAT(pool_async_data_writes) / FLOAT(pool_data_writes)) * 100,5,2)
- ELSE NULL
- END AS PAGE_CLN_RATIO,
- pool_index_writes, pool_async_index_writes,
- CASE WHEN pool_index_writes > 0
- THEN DEC((FLOAT(pool_async_index_writes) / FLOAT(pool_index_writes)) * 100,5,2)
- ELSE NULL
- END AS IND_CLN_RATIO,
- pool_no_victim_buffer, pool_lsn_gap_clns,
- pool_drty_pg_steal_clns, pool_drty_pg_thrsh_clns
- FROM BPMETRICS"
以上查询中的 Data 和 Index 页面清理率应该接近 100%。如果该值小于 90%,那么页面清洗的速度将无法跟上系统中脏页的增长速度。
dirty_page_steal_clns 显示了脏页面窃取情况。理想情况下,该值必须为零。如果它超过逻辑读取数量的一小部分,则需要清洁更多的页面。请降低 CHG_PGS_THRESHOLD,并确保有足够的 I/O 清洁器。
pool_no_victim_buffer 显示了代理在可用页面列表中无法找到可用页面的次数。如果这个数字超过逻辑读取几个百分点,那么它就是有害的。请降低 CHG_PGS_THRESHOLD,并确保有足够的 I/O 清洁器。
步骤 8:调优预取活动
类似于页面清理,我们还需要调优预取活动。在一个真正的 OLTP 环境中,预取可能没有用。但在 DSS 类的工作负载中,预取发挥着重要的作用。在理想的情况下,我们希望 IO_SERVERS IO_SERVERS(预取器)负责所有读取,该操作实质上是异步进行的。下面的查询显示了由 IO_SERVERS 完成的 I/O 读取百分比。
清单 9. 预取活动
- db2 "WITH BPMETRICS AS (
- SELECT bp_name,
- pool_data_p_reads, pool_async_data_reads,
- pool_temp_data_p_reads,
- pool_index_p_reads, pool_async_index_reads
- FROM TABLE(MON_GET_BUFFERPOOL('',-2)) AS METRICS)
- SELECT
- VARCHAR(bp_name,20) AS bp_name,
- pool_data_p_reads, pool_async_data_reads,
- CASE WHEN pool_data_p_reads > 0
- THEN DEC((FLOAT(pool_async_data_reads) / FLOAT(pool_data_p_reads +
- POOL_TEMP_DATA_P_READS)) * 100,5,2)
- ELSE NULL
- END AS PREFETCH_RATIO,
- pool_index_p_reads, pool_async_index_reads,
- CASE WHEN pool_index_p_reads > 0
- THEN DEC((FLOAT(pool_async_index_reads) / FLOAT(pool_index_p_reads)) * 100,5,2)
- ELSE NULL
- END AS PREFETCH_IDX_RATIO
- FROM BPMETRICS"
大于 90% 的值对于 PREFETCH_RATIO 而言是适合的值。
结束语
虽然这些步骤没有涵盖可能会出现的所有性能问题,但上面的方法主要侧重于解决性能问题所使用的原则和策略。遵循这些步骤会帮助您缩小问题的范围,并最终帮您解决问题。