[英]Improve performance of Ruby script processing CSV
我編寫了一個Ruby腳本來執行以下操作:
在我看來,這似乎是最容易和最合理的方式。 這個過程需要可以配置並定期重復,因此腳本。 我正在使用SQLite,因為數據將始終以CSV格式(無法訪問原始數據庫),並且將處理卸載到(容易更改的)SQL語句更容易。
問題是步驟1和2需要很長時間。 我一直在尋找提高SQLite性能的方法 。 我已經實施了其中一些建議,但收效甚微。
PRAGMA synchronous = OFF
PRAGMA journal_mode = MEMORY
(在使用內存數據庫時不確定這是否有幫助) 完成所有這些后,我得到以下時間:
假設我使用的語言不同於上面提到的帖子,並且存在編譯/解釋等差異,但插入時間約為79,000對比12,000記錄/秒 - 這比6倍慢。
我也試過索引一些(或所有)字段。 這實際上具有相反的效果。 索引花費的時間太長,以至於查詢時間的任何改進都完全被索引時間所掩蓋。 此外,由於需要額外的空間,執行內存數據庫最終會導致內存不足錯誤。
SQLite3不是這個數據量的正確數據庫嗎? 我嘗試過使用MySQL,但性能更差。
最后,這是一個嚴格的代碼版本(刪除了一些無關的細節)。
require 'csv'
require 'sqlite3'
inputFile = ARGV[0]
outputFile = ARGV[1]
criteria1 = ARGV[2]
criteria2 = ARGV[3]
criteria3 = ARGV[4]
begin
memDb = SQLite3::Database.new ":memory:"
memDb.execute "PRAGMA synchronous = OFF"
memDb.execute "PRAGMA journal_mode = MEMORY"
memDb.execute "DROP TABLE IF EXISTS Area"
memDb.execute "CREATE TABLE IF NOT EXISTS Area (StreetName TEXT, StreetType TEXT, Locality TEXT, State TEXT, PostCode INTEGER, Criteria1 REAL, Criteria2 REAL, Criteria3 REAL)"
insertStmt = memDb.prepare "INSERT INTO Area VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)"
# Read values from file
readCounter = 0
memDb.execute "BEGIN TRANSACTION"
blockReadTime = Time.now
CSV.foreach(inputFile) { |line|
readCounter += 1
break if readCounter > 100000
if readCounter % 10000 == 0
formattedReadCounter = readCounter.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
print "\rReading line #{formattedReadCounter} (#{Time.now - blockReadTime}s) "
STDOUT.flush
blockReadTime = Time.now
end
insertStmt.execute (line[6]||"").gsub("'", "''"), (line[7]||"").gsub("'", "''"), (line[9]||"").gsub("'", "''"), line[10], line[11], line[12], line[13], line[14]
}
memDb.execute "END TRANSACTION"
insertStmt.close
# Process values
sqlQuery = <<eos
SELECT DISTINCT
'*',
'*',
Locality,
State,
PostCode
FROM
Area
GROUP BY
Locality,
State,
PostCode
HAVING
MAX(Criteria1) <= #{criteria1}
AND
MAX(Criteria2) <= #{criteria2}
AND
MAX(Criteria3) <= #{criteria3}
UNION
SELECT DISTINCT
StreetName,
StreetType,
Locality,
State,
PostCode
FROM
Area
WHERE
Locality NOT IN (
SELECT
Locality
FROM
Area
GROUP BY
Locality
HAVING
MAX(Criteria1) <= #{criteria1}
AND
MAX(Criteria2) <= #{criteria2}
AND
MAX(Criteria3) <= #{criteria3}
)
GROUP BY
StreetName,
StreetType,
Locality,
State,
PostCode
HAVING
MAX(Criteria1) <= #{criteria1}
AND
MAX(Criteria2) <= #{criteria2}
AND
MAX(Criteria3) <= #{criteria3}
eos
statement = memDb.prepare sqlQuery
# Output to CSV
csvFile = CSV.open(outputFile, "wb")
resultSet = statement.execute
resultSet.each { |row| csvFile << row}
csvFile.close
rescue SQLite3::Exception => ex
puts "Excepion occurred: #{ex}"
ensure
statement.close if statement
memDb.close if memDb
end
請隨意嘲笑我天真的Ruby編碼 - 什么不殺我,希望能讓我成為一個更強大的編碼器。
通常,如果可能,應該嘗試使用UNION ALL
而不是UNION
,這樣就不必檢查兩個子查詢是否有重復項。 但是,在這種情況下,SQLite必須在單獨的步驟中執行DISTINCT
。 這是否更快取決於您的數據。
根據我的EXPLAIN QUERY PLAN
實驗,以下兩個索引應該對此查詢有所幫助:
CREATE INDEX i1 ON Area(Locality, State, PostCode);
CREATE INDEX i2 ON Area(StreetName, StreetType, Locality, State, PostCode);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.