2012-06-14 130 views
0

所以我有一點性能問題。我製作了一個構建數據庫的java程序。問題是加載數據時。我將5,000個文件加載到一個sql數據庫中。當程序啓動時,它可以在10分鐘內處理大約10%的文件,但隨着程序的進行速度會變慢。目前這一比例爲28%,按現在的速度在16小時內完成。不過,這一速度正在顯着減慢。Java和MySQL性能問題

我的問題是爲什麼程序運行時逐漸變慢,以及如何解決這個問題。

編輯:我有兩個版本。一個是螺紋(用5個螺紋蓋住),另一個不是。兩者之間的差異可以忽略不計。如果任何人喜歡,我可以再次發佈代碼,但我拿出來了,因爲我現在相當確定瓶頸是MySQL(也適當地重新標記)。我繼續使用批量插入。這確實導致了速度的初步提高,但是在處理大約30%的數據後,它又迅速下降了。

所以SQL點

  1. 我對所有64臺引擎是InnoDB的版本10
  2. 表有大約30萬行此時(〜數據的30%)
  3. 所有的表一個「聯合」主鍵。一個id和一個日期。
  4. 看着MySQL WorkBench我發現每個線程都有一個查詢(5個查詢)
  5. 我不確定時間單位(剛剛從MySQL管理員處讀取),但查詢是否已經插入文件正在採取`300。 (這個查詢應該很快,因爲它是一個從MyTable Limit 1到1的SELECT MyIndex,其中Date = date。)由於我一直在啓動和停止我在此檢查中構建的程序,以查看文件是否已經插入。通過這種方式,我可以在每次更改後啓動它,並查看是否有任何改進,而無需再次啓動該過程。
  6. 我相當肯定,性能的下降與表格的大小有關。 (我現在可以停止並啓動程序,但程序仍然很慢,只有當桌子很小時,程序才能以可接受的速度運行。)
  7. 請問,我會發布您需要的任何信息。

完成!那麼我只是讓它運行它需要的4天。謝謝大家的幫助。

乾杯,

--Orlan

+0

多少條記錄在一個文件? – maasg

+0

有20年的季度數據;所以80個時期。每個表中有64個表格。每個表格每個表格10000-8000行(也稱爲每個文件)。所以一個表應該是10,000 * 80 = 800,000行。每桌有10-150場。所有字段都是INT或BIGINT。 – Orlan

回答

1

Q1:爲什麼程序逐漸變慢?

在你的問題的空間,你有2個系統交互:一個生產,從文件系統讀取和產生數據和消費者那就是把這個數據到數據庫記錄和存儲他們。你的代碼目前是硬連接這兩個進程,而你的系統以兩者中最慢的速度工作。

在你的程序中,你有一個固定的到達率(1 /秒 - 等待超過10個線程運行)。如果在填充的表中有索引,隨着表的增大,插入將花費更長的時間。這意味着,雖然您的到達率固定爲1 /秒,但您的退出率會持續增加。因此,您將創建越來越多的線程,這些線程共享相同的CPU/IO資源,並且每單位時間完成更少的事情。創建線程也是一個非常昂貴的操作。

問題2:它可能與我如何構造來自字符串的查詢有關嗎?

只有部分。您的字符串操作是系統中的固定成本。它增加了服務一個請求所需的成本。但字符串操作的CPU是有限的,你的問題是I/O有界,這意味着改善字符串處理(你應該)只會稍微提高系統的性能。 (See Amdahl's Law)。

Q3:如何從數據庫插入過程修復(性能問題)

(FileReaderProducer) - >隊列 - >(DBBulkInsertConsumer)

  • 不要創建新的線程。使用java.util.concurrent包提供的工具,如上面提到的執行器服務或Completion服務。對於「裸露」線程池,請使用Executors工廠。

  • 對於這個特定的proble,有2個獨立的線程池(一個爲消費者,一個是製片人)將允許您調整系統以實現最佳性能。文件讀取通過並行化(直到您的I/O限制)得到改進,但db插入不是(I/O +索引+關係一致性檢查),所以您可能需要將文件讀取線程數量符合插入率(2-3)。您可以監視隊列大小以評估系統性能。

  • 使用JDBC批量插入:http://viralpatel.net/blogs/batch-insert-in-java-jdbc/
  • 使用StringBuilder而不是字符串連接。 Java中的字符串是不可變的。這意味着每次你做:myString += ",";你正在創建一個新的字符串,並使舊的字符串可以垃圾收集。反過來,這會增加垃圾收集性能的懲罰。
+0

我現在正在使用LOAD INFILE查詢而不是構建那個巨大的字符串。我開始修復線程。 – Orlan

0

有一些在你的代碼的東西,可能有助於速度的問題,你在懷疑該字符串發揮作用是正確的。例如:

以下面的代碼爲例:

String rowsString =「」;對於(int i = 0; i < = numberOfRows - 3; i ++){ rowsString + =「(DATA),\ n」; //一個額外的1在末尾沒有逗號 // // - } rowsString + =「(DATA)」;

根據有多少行,這是一個潛在的瓶頸和記憶豬。我認爲這是最好的,如果你在這裏使用StringBuilder。我看到很多更適合於StringBuilder的String操作。可能我建議你閱讀String處理一下,並優化這些,特別是你+ = Strings的地方?

那麼接下來的問題是你的餐桌是如何設計的?可能會有東西讓你的插入變慢,比如varchars的默認長​​度不正確,沒有索引或者索引太多等等。

+0

每個文件中大約有7-10k行。確實,該程序確實佔用了大量的堆棧空間,但這並不會影響性能。插入查詢佔用的時間越來越多。 – Orlan

1

你可以使用從文件直接插入數據庫(讀取here)。它工作得更快。當我爲postgres做同樣的事時,我得到了20倍的性能提升。

而且還可以調整您的套件分析器並對應用程序進行性能分析。比你會看到什麼需要你的時間。更有效地,如果

+0

我也建議在插入之前,禁用索引('改變表TBL禁用keys'),並在結束使能('改變表TBL啓用keys')。如果在插入時不需要從表中讀取數據,則可能會提高速度。 – Barranka

+0

alexey28:問題是我讀的csv文件包含太多的信息。前30個左右的細胞是不需要的。而且我必須每次都添加一個單元格。 – Orlan

+0

在LOAD DATA INFILE中,您可以指定列集,忽略從哪些行開始/停止讀取等等。它靈活靈活。換句話說,如果它不能滿足你的請求,它將更快地從初始文件中讀取,保存爲直接加載到mysql的格式,而不是加載。與db插入相比,文件操作要快得多。 – alexey28

0

大多數數據庫負荷數據,

  • 你在數據的批量加載,
  • 你在線程的相對小的numebr加載例如一個或兩個。

當你添加更多的線程會增加更多的開銷,所以你期望它會變慢。

嘗試使用具有固定大小池的ExecutorService,例如2-4,並嘗試在交易中一次加載批量爲100的數據。

+0

好的。我將線程限制爲兩個。然而,再次下降30%,實在太慢了。當我說得太慢時,我的意思是完成需要10個小時以上。 (我在代碼中的ETA估計值不斷增加,這可能需要超過10個小時。) – Orlan

+0

您可能達到了磁盤無法滿足的限制(我可能錯了,因爲我認爲大多數數據庫始終提交)我在許多程序中看到的是實際寫入磁盤的數量遠遠低於應用程序嘗試寫入的數量。運行良好,直到達到限制(Centos上的內存大約是內存的1/5),性能下降到寫入磁盤的速度。當應用程序停止時,我看到有一段時間後寫入。 –

+0

您應該仍然可以看到通過批量處理數據而獲得的改進,但是我懷疑您已經達到了磁盤可寫入的限制。你可以檢查你有寫緩存啓用? –

0

您有加快數據庫訪問幾個很好的嘗試和測試選項。

  1. 使用ExecutorService作爲您的線程。這可能無助於速度,但它可以幫助您實施以下內容。
  2. ThreadLocalConnection,而不是使每個文件一個新的連接。另外,顯然,不要關閉它。
  3. 創建一個PreparedStatement,而不是把一個新的周圍每次。
  4. 批量處理您的語句執行。