示例2:错误的Hibernate配置造成了过多的SQL执行
据我所知,Hibernate或其他O/R映射器有许多使用者。我想要提醒你们一点,O/R映射器所提供的延迟加载与贪婪加载选项,以及其他各种缓存层各有其存在的理由。对于特定的用例,需要确保你正确地使用了这些特性与选项。
在下面这个示例中,延迟加载并不是一种好的选择,因为加载2千个对象以及他们的属性会导致产生4千多次SQL查询。考虑到我们总是需要获取所有对象,那么更好的方式是贪婪加载这些对象,然后考虑对他们进行缓存,前提是这些对象不会变更得十分频繁:
(点击放大图像)
在使用Hibernate或Spring等O/R映射器时,需要选择正确的加载与缓存选项。你需要理解他们的幕后工作原理。
大多数O/R映射器都会通过日志记录提供优秀的诊断选项,同时也可以查看在线社区中的内容,以了解各种最佳实践。推荐你阅读由Alois Reitbauer撰写的一系列博客文章,他曾经在Hibernate推出的早些年头对其进行过非常深入的研究。在这系列文章中,他特别强调了如何有效地使用缓存与加载选项。
示例3:在自定义DB访问代码中使用的语句未经过预处理
当数据库引擎完成对某条SQL语句的解析,并创建了数据访问的执行计划后,该结果会被保存在数据库中的一个缓存区域中以便重用,而无需重新解析这一语句(语句解析是数据库中最耗费CPU时间的操作)。用于在缓存中找到某个查询的键是语句的全文本。这也意味着,如果你调用了1000次相同的语句,却为其传了100个不同的参数值(例如where语句中的值),那么在缓存中就会产生1000个不同的条目,而使用了新参数的第1001次查询也必须被再次解析。这种工作方式非常低效。因此,我们提出了“预处理的语句”这一概念:某条语句经过预处理、解析后被保存在缓存中,以占位符的方式表示变量。在这条语句的实际执行过程中,这些占位符会被实际的值所替换,无需再次解析这条语句,可以直接从缓存中找出执行计划。
数据库访问框架通常在这一点上做得很出色,会对查询语句进行预处理。但在自定义代码中,我发现开发者经常会忽略这一点。在以下示例中,只有一小部分SQL执行经过了预处理过程:
(点击放大图像)
通过对SQL执行次数与已预处理的SQL执行次数进行对比,发现了未经预处理的数据库访问的问题
如果你打算自行开发数据库访问代码,请再次确认你正确地调用了prepareStatement。举例来说,如果你调用某个查询不止1次,那么通常来说最好能够使用PreparedStatement。如果你选择使用框架以访问数据,也请再次确认这些框架的行为,以及在优化和执行所生成的SQL时有哪些配置选项可以选择。实现一点最简单的方式是对executeStatement与prepareStatement执行的次数进行监控。如果你重复对每个SQL查询进行相同的监控,那么将很容易地找到优化热点。
示例4:由于耗时的后端SQL报表执行,造成连接池无法有效地调整大小
我经常发现有些应用会使用默认的连接池大小,例如每个池10或20个连接。开发者总是会忽略对连接池大小的优化,因为他们没有进行必要的大规模负载测试,也不知道有多少个用户会使用这些新特性,更不了解并行的DB访问会导致什么结果。也有可能是从预发布环境转向生产环境的部署时“丢失”了连接池的配置信息,导致生产环境中的配置使用了应用服务器中的默认配置。
通过JMX指标信息,能够方便地对连接池的使用情况进行监控。每种应用服务器(Tomcat、JBoss、Websphere等等)都会提供这些指标,不过有些服务器需要你明确地开启这种特性。下图展示了某个群集中的WebLogic服务器的连接池使用情况。你可以看到,在其中三台应用服务器中, “活动的DB连接数量”都已经达到最大值。
(点击放大图像)
原文转自:http://www.infoq.com/cn/articles/Diagnosing-Common-Java-Database-Performance-Hotspots