Summary:
当启用批量获取时,Hibernate 会准备大量查询:这些查询会占用大量内存,而这些内存无法被垃圾化。批量大小为 1000 时大约需要 150 Mo 的 RAM。
因此,最好使用较低的常规批量大小(如 10、20 或 40),仅使用 @BatchSize 注释为特定集合设置更大的批量大小。
Detail:
这里解释了获取批量大小了解 Hibernate 中的 @BatchSize https://stackoverflow.com/questions/25210949/understanding-batchsize-in-hibernate,“hibernate.default_batch_fetch_size”是通用参数,“@BatchSize”注释允许覆盖特定关联上的通用参数。
但这些解释并没有真正回答“为什么官方文档推荐值 4、8 或 16”的问题?显然,现代数据库可以处理 IN 子句中包含远远超过 16 个值的查询,并且在 IN 子句中使用比方说 1000 个值进行查询将允许执行更少的查询,从而获得更好的性能...那么为什么不设置批量大小为 1000?
我做到了,我将 1024 设置为批量大小,答案很快就出现了:tomcat 服务器需要更多时间来启动,并且在调试日志中我可以看到很多带有“静态选择实体...”的行。
发生的情况是 Hibernate 准备了数千个静态查询,以下是实体的部分日志:
...
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?, ?, ?, ?, ?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?, ?, ?, ?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?, ?, ?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?, ?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id in (?, ?)
Static select for entity Profile [PESSIMISTIC_READ]: select xxx_ with (holdlock, rowlock ) where id = ?
...
正如您所看到的,Hibernate 准备批量获取请求,但不是为所有请求准备。 Hibernate 准备所有 1,2,3....10 个参数的请求,然后只准备参数数量等于batchSize/(2^n) 的请求。例如,如果batchSize=120 => 120, 60, 30, 15, 10, 9, 8, ..., 2, 1
所以我尝试批量获取具有不同数量元素的集合,结果是:
为了获取 18 个项目,hibernate 进行了 2 次查询:一次查询 16 个项目,另一次查询 2 个项目。
为了获取 16 个项目,hibernate 对 16 个项目进行了 1 次查询。
为了获取 12 个项目,hibernate 进行了 2 次查询:一次查询 10 个项目,另一次查询 2 个项目。
Hibernate只使用启动时准备的语句。
之后,我监控了所有这些准备好的语句的 RAM 使用情况:
与batchSize = 0 => 94 Mo(这是我的参考)
batchSize = 32 => 156 Mo(+62 Mo 与参考)
batchSize = 64 => 164 Mo(+68 Mo 与参考)
batchSize = 1000 => 250 Mo(+156!Mo与参考)
(我的项目规模中等,大约300个实体)
现在是时候了结论:
1) 批量大小可以有对启动时间和内存影响很大消耗。它不随批量大小线性扩展,批量大小 80 的成本是批量大小 10 的 2 倍。
2)Hibernate无法检索具有任何大小的批次的项目集合,它仅使用准备好的批次查询。如果设置batchSize = 120,则准备好的查询将是带有120、60、30、15、10、9、8、7、6、5、4、3、2和1个参数的查询。因此,如果您尝试获取包含 220 个项目的集合,则会触发 4 个查询:第一个查询将检索 120 个项目,第二个检索 60 个项目,第三个检索 30 个项目,第四个查询检索 10 个项目。
这解释了为什么推荐的批量大小较低。我建议设置一个较低的全局batchSize,例如20(对我来说20似乎比16更好,因为它不会生成比16更多的准备好的查询),并且仅在需要时设置特定的更大的@BatchSize。
(我使用的是Hibernate 5.1)