SELECT语句使用JDBC和Hibernate批量获取
介紹
現(xiàn)在,我已經(jīng)介紹了Hibernate對(duì)INSERT , UPDATE和DELETE語(yǔ)句的批處理支持,是時(shí)候分析SELECT語(yǔ)句結(jié)果集的批量提取了。
JDBC ResultSet提供了一個(gè)客戶端代理游標(biāo),用于獲取當(dāng)前語(yǔ)句的返回?cái)?shù)據(jù)。 執(zhí)行該語(yǔ)句后,必須將結(jié)果從數(shù)據(jù)庫(kù)游標(biāo)傳輸?shù)娇蛻舳恕?此操作可以立即執(zhí)行,也可以根據(jù)需要執(zhí)行。
ResultSet游標(biāo)有三種類型 :
| TYPE_FORWARD_ONLY | 這是默認(rèn)的ResultSet游標(biāo)類型。 結(jié)果集只能向前移動(dòng),并且結(jié)果數(shù)據(jù)可以一次獲取,也可以在迭代游標(biāo)時(shí)檢索。 數(shù)據(jù)庫(kù)可以決定在查詢開(kāi)始時(shí)還是在獲取時(shí)獲取可用的數(shù)據(jù)。 |
| TYPE_SCROLL_INSENSITIVE | 可以向前和向后滾動(dòng)結(jié)果集,并且結(jié)果數(shù)據(jù)對(duì)游標(biāo)仍處于打開(kāi)狀態(tài)時(shí)發(fā)生的并發(fā)更改不敏感 |
| TYPE_SCROLL_SENSITIVE | 可以向前和向后滾動(dòng)結(jié)果集,并且結(jié)果數(shù)據(jù)對(duì)在游標(biāo)仍處于打開(kāi)狀態(tài)時(shí)發(fā)生的并發(fā)更改敏感 。 因此,數(shù)據(jù)是按需獲取的,而不是從數(shù)據(jù)庫(kù)游標(biāo)緩存中獲取的 |
并非所有數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序都實(shí)現(xiàn)所有游標(biāo)類型,并且批處理獲取行為是通過(guò)JDBC語(yǔ)句 fetchSize屬性控制的,根據(jù)Javadoc :
當(dāng)此Statement生成的ResultSet對(duì)象需要更多行時(shí),向JDBC驅(qū)動(dòng)程序提供有關(guān)應(yīng)該從數(shù)據(jù)庫(kù)中獲取的行數(shù)的提示。 如果指定的值為零,則忽略提示。 默認(rèn)值為零。
因此,默認(rèn)的獲取策略是特定于數(shù)據(jù)庫(kù)的,從應(yīng)用程序性能的角度來(lái)看,這方面在調(diào)整數(shù)據(jù)訪問(wèn)層時(shí)非常重要:
- 甲骨文 默認(rèn)情況下, Oracle JDBC運(yùn)行查詢時(shí),它一次從數(shù)據(jù)庫(kù)游標(biāo)中檢索10行的結(jié)果集。根據(jù)Oracle JDBC驅(qū)動(dòng)程序文檔 :“合理的”取決于應(yīng)用程序的詳細(xì)信息。 Oracle建議fetchSize不超過(guò)100,盡管在某些情況下可能更合適。 對(duì)于某些查詢,即使返回許多行, fetchSize可能也會(huì)過(guò)大。
- 的MySQL 默認(rèn)情況下, 結(jié)果集是完全檢索和存儲(chǔ)在內(nèi)存中。 在大多數(shù)情況下,這是最有效的操作方式,并且由于MySQL網(wǎng)絡(luò)協(xié)議的設(shè)計(jì),因此更易于實(shí)現(xiàn)。
- SQL服務(wù)器 通常,當(dāng)用于SQL Server的Microsoft JDBC驅(qū)動(dòng)程序執(zhí)行查詢時(shí),驅(qū)動(dòng)程序會(huì)將所有結(jié)果從服務(wù)器檢索到應(yīng)用程序內(nèi)存中。 盡管這種方法最大程度地減少了SQL Server上的資源消耗,但它可以在JDBC應(yīng)用程序中引發(fā)產(chǎn)生非常大結(jié)果的查詢的OutOfMemoryError 。
- PostgreSQL的 默認(rèn)情況下,驅(qū)動(dòng)程序會(huì)立即收集查詢的所有結(jié)果。 這對(duì)于大型數(shù)據(jù)集可能很不方便,因此JDBC驅(qū)動(dòng)程序提供了一種將ResultSet基于數(shù)據(jù)庫(kù)游標(biāo)并且僅獲取少量行的方法。
- DB2 默認(rèn)情況下,驅(qū)動(dòng)程序會(huì)立即收集查詢的所有結(jié)果。 這對(duì)于大型數(shù)據(jù)集可能很不方便,因此JDBC驅(qū)動(dòng)程序提供了一種將ResultSet基于數(shù)據(jù)庫(kù)游標(biāo)并且僅獲取少量行的方法。 fetchSize屬性與queryDataSize屬性不同。 fetchSize影響返回的行數(shù),而queryDataSize影響返回的字節(jié)數(shù)。
例如,如果結(jié)果集大小為50 KB,而queryDataSize的值為32767(32KB),則需要兩次到數(shù)據(jù)庫(kù)服務(wù)器的行程才能檢索結(jié)果集。 但是,如果將queryDataSize設(shè)置為65535(64 KB),則只需一趟數(shù)據(jù)源即可檢索結(jié)果集。
Java Persistence Query接口通過(guò)Query.getResultList()方法調(diào)用僅提供全結(jié)果檢索。
Hibernate還通過(guò)其特定的Query.scroll() API支持可滾動(dòng)的ResultSet游標(biāo)。
可滾動(dòng)的ResultSets唯一明顯的優(yōu)點(diǎn)是,由于可以按需獲取數(shù)據(jù),因此我們可以避免客戶端的內(nèi)存問(wèn)題。 這聽(tīng)起來(lái)似乎是很自然的選擇,但實(shí)際上,由于以下原因,您不應(yīng)該獲取大型結(jié)果集:
- 結(jié)果集較大會(huì)占用大量數(shù)據(jù)庫(kù)服務(wù)器資源,并且由于數(shù)據(jù)庫(kù)是高度并發(fā)的環(huán)境 ,因此可能會(huì)妨礙可用性和可伸縮性
- 表的大小趨于增長(zhǎng),適度的結(jié)果集可能很容易變成很大的表。 這種情況發(fā)生在生產(chǎn)系統(tǒng)中,很早就發(fā)布了應(yīng)用程序代碼。 由于用戶只能瀏覽整個(gè)結(jié)果集中的一小部分,因此分頁(yè)是一種更具可伸縮性的數(shù)據(jù)提取方法
- 過(guò)于常見(jiàn)的偏移分頁(yè)不適用于大型結(jié)果集(因?yàn)轫憫?yīng)時(shí)間隨頁(yè)碼線性增加),并且在遍歷大型結(jié)果集時(shí)應(yīng)考慮鍵集分頁(yè) 。 鍵集分頁(yè)提供了恒定的響應(yīng)時(shí)間 ,對(duì)正在獲取的頁(yè)面的相對(duì)位置不敏感
- 即使對(duì)于批處理作業(yè) ,將處理項(xiàng)目限制為適當(dāng)?shù)呐幚泶笮】偸潜容^安全的。 大批量可能導(dǎo)致內(nèi)存問(wèn)題或?qū)е麻L(zhǎng)時(shí)間運(yùn)行的事務(wù),從而增加了撤消/重做事務(wù)日志的大小
測(cè)試時(shí)間
我們的域?qū)嶓w模型如下所示:
以下測(cè)試將用于驗(yàn)證各種結(jié)果集的獲取行為:
@Test public void testFetchSize() {doInTransaction(session -> {int batchSize = batchSize();for(int i = 0; i < itemsCount(); i++) {Post post = new Post(String.format("Post no. %d", i));int j = 0;post.addComment(new Comment(String.format("Post comment %d:%d", i, j++)));post.addComment(new Comment(String.format("Post comment %d:%d", i, j++)));session.persist(post);if(i % batchSize == 0 && i > 0) {session.flush();session.clear();}}});long startNanos = System.nanoTime();LOGGER.info("Test fetch size");doInTransaction(session -> {List posts = session.createQuery("select p " +"from Post p " +"join fetch p.comments ").list();LOGGER.info("{}.fetched {} entities",getClass().getSimpleName(),posts.size());});LOGGER.info("{}.testFetch took {} millis",getClass().getSimpleName(),TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos)); }要將Hibernate配置為使用顯式Statement fetchSize ,我們需要設(shè)置以下Hibernate屬性:
properties.put("hibernate.jdbc.fetch_size", fetchSize());每個(gè)測(cè)試將插入5000個(gè)Post實(shí)體,每個(gè)實(shí)體具有2個(gè)Comment 。
針對(duì)商業(yè)數(shù)據(jù)庫(kù)運(yùn)行第一個(gè)測(cè)試,結(jié)果如下:
| 1個(gè) | 1190 |
| 10 | 640 |
| 100 | 481 |
| 1000 | 459 |
| 10000 | 449 |
| 默認(rèn)值(10) | 545 |
提取大小越大,則提取整個(gè)結(jié)果集所需的往返行程越少。 如果返回的行包含許多列,則較大的訪存大小將按比例需要較大的數(shù)據(jù)庫(kù)緩沖區(qū)。
第二輪測(cè)試針對(duì)PostgreSQL 9.4運(yùn)行,結(jié)果如下:
| 1個(gè) | 1181 |
| 10 | 572 |
| 100 | 485 |
| 1000 | 458 |
| 10000 | 437 |
| 默認(rèn)(全部) | 396 |
即使fetchSize等于要返回的總行數(shù),默認(rèn)的fetch大小也會(huì)產(chǎn)生最佳結(jié)果。 由于沒(méi)有上限緩沖區(qū)限制,因此在檢索大型結(jié)果集時(shí),默認(rèn)的提取大小可能會(huì)導(dǎo)致OutOfMemoryError問(wèn)題。
結(jié)論
雖然大多數(shù)數(shù)據(jù)庫(kù)服務(wù)都不會(huì)對(duì)結(jié)果集的獲取大小施加默認(rèn)上限,但是最好限制整個(gè)結(jié)果集(如果要求允許的話)。 大小有限的結(jié)果集應(yīng)解決無(wú)限制的獲取大小缺陷,同時(shí)即使在查詢的數(shù)據(jù)逐漸增長(zhǎng)的情況下,也要確??深A(yù)測(cè)的響應(yīng)時(shí)間。 查詢?cè)蕉?#xff0c;行級(jí)鎖被釋放的越快,數(shù)據(jù)訪問(wèn)層的可伸縮性就越高 。
- 代碼可在GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2015/04/select-statements-batch-fetching-with-jdbc-and-hibernate.html
總結(jié)
以上是生活随笔為你收集整理的SELECT语句使用JDBC和Hibernate批量获取的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 鞋柜的设置(鞋柜的设置弱电箱)
- 下一篇: 如何使用Hibernate批处理INSE