8 S2 E' _5 w Z: F# t ) Q: h! l4 A5 u; r
常用的速成法 ( R9 C2 U( `/ [1 [& A
下面介绍的速成法能找出 Spring/Hibernate 应用中常见的性能问题,以及对应的解决方案。 ( u% @/ N; L0 M; U1 \1 k
4 d9 F. K, T! ?3 V! T; K
! S8 z8 O' [2 j, D# l4 [速成法1——减少生成主键的代价 ! M( [+ { l& s1 {% C! R& x在插入操作频繁的进程中,主键的生成策略很重要。生成 id 的一种常见方法是使用数据库序列,通常一张表一个 id,从而避免在不同表间进行插入时的冲突。 + s8 v( m2 |$ l2 L6 _- V! e 0 c1 z% E: n8 I9 w9 r, F. d7 B; i * i! H7 }0 v% J' O+ h b问题在于,如果要插入50条记录,我们希望为了获取这50个 id,可以避免50趟查询数据库的来回网络调用,让 Java 进程不一直等待。 . [3 r( K' Y1 G A; t' V 4 n7 x6 c7 G5 x, W& {) X$ ^ : U8 M6 K B5 M5 t' a
Hibernate 通常如何解决此问题? ' ^" [2 ?) t) ]: v5 U7 rHibernate 提供了优化的 ID 生成器以避免此问题。也即,对于序列,会默认使用 HiLo id 生成器。以下是 HiLo 序列生成器的工作方式: ' v6 k7 i0 {6 n
% p6 {, K3 k# J7 @# |9 O9 ^
5 J! A+ ^& S( P( K1 D5 X
调用一次序列,获得 1000 (高值)
用以下方式计算50个 id % U! s' z: J5 m4 W- H; l
8 b/ ~8 |) ] O4 m6 s, Q: v( x1000 * 50 + 0 = 50000 . `. o; O, m6 l4 B. _ ; v; v: B8 c& Z
^5 ?' k) s& R* f& h0 ^
1000 * 50 + 1 = 50001 9 F! P( f! S7 }' M" q - b' U2 @3 N+ |" o/ ? ) g7 I! V/ C4 ~) n... 2 J1 N. z! l. Z) I* B6 ]
+ v( B @/ w7 b" S; e
- w! K8 x' k* J1 p0 s8 h1000 * 50 + 49 = 50049, 达到低值 (50) # c$ r2 G' A3 u+ h3 j4 J
: a, n- f. x* A( a) T
8 V8 }$ g7 B5 C- j' E4 q为新的高值1001调用序列,依次类推 ' }+ R5 r- p) w4 Z6 A
: n2 r) G5 B1 I/ z- g/ K. P
! I5 X+ f# c. z& X% _, l7 A! g& e
因此一次序列调用,可生成50个键,从而减少数次来回网络调用导致的负担。 : H9 [+ D$ t8 S3 \% a J3 x2 M ) G2 v/ Y' p# u% I- t+ M " y: Z* O% y5 e0 T% w. h4 x+ @
这些优化的键生成器默认在 Hibernate 4中开启。如要禁用,可将 hibernate.id.new_generator_mappings 设置为 false。 ; j3 V5 K9 {; Q# U/ i
" v" r# m7 n V0 ^' p / Z/ D$ \! a2 o" |4 T: v
为什么生成主键仍是一个问题? 3 k/ s7 L5 X0 J6 t3 e问题在于,如果你声明键生成策略为 AUTO,且未启用优化的键生成器,那么应用最后会面临大量的序列调用。 ( F& H7 Z7 Z, d- i) P6 {# {8 ? 1 k2 L0 j1 f, K/ f9 M: P8 m6 e 6 N* s: d" n' N" |4 h- d
为了确保启用优化的键生成器,请将键生成策略改为 SEQUENCE 而非 AUTO。 3 X( A( F9 d: M, Q+ |4 w# X" y) ~4 E ; C" F0 I4 }3 l7 c' U # z. x* H7 n. `$ L& h' p
@Id & F" n4 e1 T# ~+ R2 |; f2 U' {8 n@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "your_key_generator") 2 P3 j; S/ W# H8 r! j8 i# }private Long id; 2 ^0 S( z; R8 ?2 w% o* y/ d改变设定之后,在插入操作频繁的应用中能看到10%到20%的性能提升,而且几乎没有改动代码。 % r7 n4 a( c9 t* _* z 1 x7 M, `3 Z5 @: ` 7 b2 [8 W. A8 X速成法2——使用 JDBC 批处理 inserts/updates 0 {) E9 o+ H1 K6 h; q1 o; {- F9 e对于批处理程序,JDBC 驱动程序提供了旨在减少网络来回传输的优化方法:”JDBC batch inserts/updates“。使用该方法后,插入或更新会先在驱动层排队,然后再传送到数据库。 ) v5 O: O4 T# k t" Y4 P4 ]9 [
6 ^9 M1 I+ k s- d8 h& o # b0 y/ g$ v9 ~' n7 v' ?+ Z6 J
当达到阈值后,所有排队的语句都会一次性传给数据库。这可以避免驱动程序逐一传送语句,导致网络来回传送的负担。 M& a0 a3 N8 ?. d/ H3 L+ Q' o/ p
, B4 O6 |4 M7 x6 F' [. A
+ K3 ~2 t" y7 I H |; V经过以下配置,就能激活批处理 inserts/updates: ! |. `6 M8 F3 ~" r ; z- E3 ?6 m: X8 g) k1 p
- B# v4 Y3 A9 n* P# V" D
<prop key="hibernate.jdbc.batch_size">100</prop> 0 C5 E2 p+ m4 s# l& J, T
<prop key="hibernate.order_inserts">true</prop> ( ]8 c9 r* ^9 H# x
<prop key="hibernate.order_updates">true</prop> , ]5 ]+ K! @7 N0 j! K7 Q7 X6 x
仅设置 JDBC 批处理大小并不够。因为 JDBC 驱动程序只会在收到对同一张表 insert/updates 时批处理这些语句。 ) d. P# {. {8 m1 {+ v
0 @3 ~5 j; f1 a+ T