最近测试发现个myBatis 有个比较严重的性能问题, 描述如下: 1. define a bean class public class Bean { private int id; private String desc; private long price; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public long getPrice() { return price; } public void setPrice(long price) { this.price = price; } } 2. 如果在这个Bean定义中,存在一个属性没有 Getter方法 在运行过程中, Mybatis 会表现为把 class Bean 确定成一个ComplexAccessPlan的对象。那么, Mybatis对Bean对象填充SQL执行后的返回结果会造成比较严重的性能问题。 复杂对象填充Bean的结果在性能上表现比较差一点。 这个性能差异随着需要设置属性数量的增加, 性能成正比的下降。 目前我测试的结果是10个属性情况下影响大约 5-6%的执行时间, 如果属性增加到 84个(中文站的offer对象),他的性能会导致超过30%的下降。 具体的原因, 我会如下解释: com.mybatis.sqlmap.engine.accessplan.AccessPlanFactory Line 60 if (bytecodeEnhancementEnabled) { try { plan = new EnhancedPropertyAccessPlan(clazz, propertyNames); } catch (Throwable t) { try { plan = new PropertyAccessPlan(clazz, propertyNames); } catch (Throwable t2) { plan = new ComplexAccessPlan(clazz, propertyNames); } } } com.mybatis.common.beans.ClassInfo Line256 public Method getGetter(String propertyName) { Method method = (Method) getMethods.get(propertyName); if (method == null) { throw new ProbeException("There is no READABLE property named '" + propertyName + "' in class '" + className + "'"); } return method; } class EnhancedPropertyAccessPlan/PropertyAccessPlan call com.mybatis.common.beans.ClassInfo.getGetter(String) that cause an exception when a bean have no Getter method, AccessPlan object choose ComplexAccessPlan. 3. 根据以上的代码, 我们还可以得出如下结论(这是我给MYBATIS开发团队的邮件部分, 不翻译:)): IBtatis automatic decide a simple bean that property have no Getter method to be Complex type. Mybatis does not prompt any warning enhancementEnable option will be skipped. I think these mybatis exception handling is not smooth. and If user's bean loose some Getter method, a common user does not know why mybatis performance become bad. 就是一个对象由于Getter方法的缺失, Mybatis把这个对象的当做复杂对象, 从而, 导致enhancementEnable=true(bean对象字节增加功能, 有兴趣的同学可以看看CGLIB中BulkBean的使用)的定义失去了任何作用, 进一步导致MYBATIS的性能下降。 针对我们发现的问题, 我们建议如下解决问题: 1. 任何被Mybatis 使用的对象属性必须定义完整的Setter/Getter方法 2. 避免使用自定义类型的对象属性 3. 如果部分属性需要被适当处理后才能使用的, 比如表中有一个字段price, 但是我们需要使用的是Money对象, 请按如下方式使用。primitivePrice作为数据库使用的属性, price作为应用程序使用的属性。 public class Bean { private Money price = null; private long primitivePrice; public Money getPrice() { if (price == null) { this.price = new Money(0, 0); this.price.setCent(primitivePrice); } return price; } public void setPrice(Money price) { if (price == null) { this.price = new Money(0, 0); } else { this.price = price; } this.primitivePrice = price.getCent(); } public void setPrimitivePrice(long price) { this.primitivePrice = price; } public long getPrimitivePrice() { return this.primitivePrice ; } } ====================================================================== Mybatis on Oracle的性能优化 我们先主要看2个参数 1.defaultRowPrefetch of oracle 2.enhancementEnabled of Mybatis 环境 1. Java HotSpot(TM) Server VM (build 1.5.0_12-b04, mixed mode) Java HotSpot(TM) Server VM (build 1.6.0_05-b13, mixed mode) 2. Intel(R) Core(TM)2 CPU T7400 @ 2.16GHz L2 4M 3. JVM OPTION -Xms512m -Xmx1024m -XX:PermSize=96m 从数据库中读取10000行, 5列数据情况, Java Bean对象大约不到100个属性。循环20次, 外加5次的赃数据。 A. defaultRowPrefetch=default enhancementEnabled=false/true 754ms/743ms B. defaultRowPrefetch=50 enhancementEnabled=false/true 389ms/382ms C. defaultRowPrefetch=100 enhancementEnabled=false/true 319ms/319ms D. defaultRowPrefetch=200 enhancementEnabled=false/true 277ms/274ms E. defaultRowPrefetch=500 enhancementEnabled=false/true 251ms/250ms F. defaultRowPrefetch=1000 enhancementEnabled=false/true 242ms/238ms G. defaultRowPrefetch=1000 enhancementEnabled=true 237ms(JAVA6) H. defaultRowPrefetch=200 enhancementEnabled=true 271MS(JAVA6) 总结以上情况, 在数据行比较多的情况下, defaultRowPrefetch值的提高, 对于性能的影响是显著的, 但是, 这个提升是牺牲很多内存为代价的, 因此, 如果过高的defaultRowPrefetch值会导致内存比较紧张。 另外值得说明的是, 在一样的参数前提下, JAVA6对于性能还是有一定的提升的。对于比较大的查询, defaultRowPrefetch经验值应该是200还是合理的。 另外, 对于enhancementEnabled选项带来的收益, 相对来说比较少。 但是,对于高压力的系统, 这是无IO等待下情况的代码执行提高这些是非常值。 以上的测试数据列数比较少, 因此在JAVA BEAN的建立上是非常的节约时间的, 我们看看在差不多100个属性的填充下的性能表现, 我们已经知道了defaultRowPrefetch带来收益的经验值。 因此, 我们设置defaultRowPrefetch=200. A. defaultRowPrefetch=200 enhancementEnabled=false 1736ms B. defaultRowPrefetch=200 enhancementEnabled=true 1721ms C. defaultRowPrefetch=50 enhancementEnabled=true 1866ms OK, enhancementEnabled继续表明对性能的提升作用很小, 但是列的数据大小对性能的影响是非常大的。 但是, 我们无法确定这个时间是消耗在Java Bean 填充上 还是列读取上。Mybatis没有具体的办法测试。 不过, 在减少结果参数说明的情况下, 性能能得到明显的提升, 我们还是可以断定, JAVA BEAN的被声明成结果映射的时候, 尽量减少结果映射的列,可以获得很高性能的提升。 因此, 使用Mybatis操作大量的数据的表, 建议只映射应该获取到的数据, 而不是全部的列。 select * from db where... 你可以取需要的列到java bean. 总而言之: select * from db where... 这样的形式对性能影响比 把所有的列映射到Java Bean 来的小! set bean property + ResultSet.getXXX(int index)的操作消耗了大部分的性能。 一些代码片段: 数据原的定义 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass"> <value>oracle.jdbc.OracleDriver</value> </property> <property name="jdbcUrl"> <value>jdbc:oracle:thin:@10.0.0.1:1521:test</value> </property> <property name="properties"> <props> <prop key="user">test</prop> <prop key="password">test</prop> <prop key="defaultRowPrefetch">50</prop> </props> </property> </bean> 为单个SQL查询定义defaultRowPrefetch, 在Mybatis的定义中为fetchSize <select id="MS-FIND-PublishedOffers-By-MemberId-Paged" resultMap="RM-OfferResult" fetchSize="200"> CGLIB增强定义 <settings cacheModelsEnabled="true" enhancementEnabled="true" lazyLoadingEnabled="false" maxRequests="3000" maxSessions="3000" maxTransactions="3000" useStatementNamespaces="false"/> |
|