我问了两个相关问题(How can I speed up fetching the results after running an sqlite query?和Is it normal that sqlite.fetchall() is so slow?)。我已经改变了一些东西并获得了一些加速,但是select语句完成仍需要一个多小时。
我有一个表feature
,其中包含rtMin
,rtMax
,mzMin
和mzMax
值。这些值一起是矩形的角(如果您阅读我的旧问题,我会分开保存这些值,而不是从convexhull
表中获取min()和max(),工作得更快)。
我得到一张表spectrum
,其中包含rt
和mz
值。当谱的rt
和mz
值位于要素的矩形中时,我有一个表格可以将要素与要素相关联。
为此,我使用以下sql和python代码来检索频谱和功能的ID:
self.cursor.execute("SELECT spectrum_id, feature_table_id "+
"FROM `spectrum` "+
"INNER JOIN `feature` "+
"ON feature.msrun_msrun_id = spectrum.msrun_msrun_id "+
"WHERE spectrum.scan_start_time >= feature.rtMin "+
"AND spectrum.scan_start_time <= feature.rtMax "+
"AND spectrum.base_peak_mz >= feature.mzMin "+
"AND spectrum.base_peak_mz <= feature.mzMax")
spectrumAndFeature_ids = self.cursor.fetchall()
for spectrumAndFeature_id in spectrumAndFeature_ids:
spectrum_has_feature_inputValues = (spectrumAndFeature_id[0], spectrumAndFeature_id[1])
self.cursor.execute("INSERT INTO `spectrum_has_feature` VALUES (?,?)",spectrum_has_feature_inputValues)
我计算了执行,取消和插入时间并获得了以下内容:
query took: 74.7989799976 seconds
5888.845541 seconds since fetchall
returned a length of: 10822
inserting all values took: 3.29669690132 seconds
所以这个查询需要大约一个半小时,大部分时间都在做fetchall()。我怎样才能加快速度呢?我应该在python代码中进行rt
和mz
比较吗?
要显示我得到的索引,这里是表格的create语句:
CREATE TABLE IF NOT EXISTS `feature` (
`feature_table_id` INT PRIMARY KEY NOT NULL ,
`feature_id` VARCHAR(40) NOT NULL ,
`intensity` DOUBLE NOT NULL ,
`overallquality` DOUBLE NOT NULL ,
`charge` INT NOT NULL ,
`content` VARCHAR(45) NOT NULL ,
`intensity_cutoff` DOUBLE NOT NULL,
`mzMin` DOUBLE NULL ,
`mzMax` DOUBLE NULL ,
`rtMin` DOUBLE NULL ,
`rtMax` DOUBLE NULL ,
`msrun_msrun_id` INT NOT NULL ,
CONSTRAINT `fk_feature_msrun1`
FOREIGN KEY (`msrun_msrun_id` )
REFERENCES `msrun` (`msrun_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION);
CREATE UNIQUE INDEX `id_UNIQUE` ON `feature` (`feature_table_id` ASC);
CREATE INDEX `fk_feature_msrun1` ON `feature` (`msrun_msrun_id` ASC);
CREATE TABLE IF NOT EXISTS `spectrum` (
`spectrum_id` INT PRIMARY KEY NOT NULL ,
`spectrum_index` INT NOT NULL ,
`ms_level` INT NOT NULL ,
`base_peak_mz` DOUBLE NOT NULL ,
`base_peak_intensity` DOUBLE NOT NULL ,
`total_ion_current` DOUBLE NOT NULL ,
`lowest_observes_mz` DOUBLE NOT NULL ,
`highest_observed_mz` DOUBLE NOT NULL ,
`scan_start_time` DOUBLE NOT NULL ,
`ion_injection_time` DOUBLE,
`binary_data_mz` BLOB NOT NULL,
`binaray_data_rt` BLOB NOT NULL,
`msrun_msrun_id` INT NOT NULL ,
CONSTRAINT `fk_spectrum_msrun1`
FOREIGN KEY (`msrun_msrun_id` )
REFERENCES `msrun` (`msrun_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION);
CREATE INDEX `fk_spectrum_msrun1` ON `spectrum` (`msrun_msrun_id` ASC);
CREATE TABLE IF NOT EXISTS `spectrum_has_feature` (
`spectrum_spectrum_id` INT NOT NULL ,
`feature_feature_table_id` INT NOT NULL ,
CONSTRAINT `fk_spectrum_has_feature_spectrum1`
FOREIGN KEY (`spectrum_spectrum_id` )
REFERENCES `spectrum` (`spectrum_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_spectrum_has_feature_feature1`
FOREIGN KEY (`feature_feature_table_id` )
REFERENCES `feature` (`feature_table_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION);
CREATE INDEX `fk_spectrum_has_feature_feature1` ON `spectrum_has_feature` (`feature_feature_table_id` ASC);
CREATE INDEX `fk_spectrum_has_feature_spectrum1` ON `spectrum_has_feature` (`spectrum_spectrum_id` ASC);
我有20938个光谱,305742个特征和2个msruns。结果是10822场比赛。
使用新索引(CREATE INDEX fk_spectrum_msrun1_2
ON spectrum
(msrun_msrun_id
,base_peak_mz
);)并保存约20秒:
查询花了:76.4599349499秒
自fetchall以来5864.15418601秒
从EXPLAIN QUERY PLAN打印:
(0, 0, 0, u'SCAN TABLE spectrum (~1000000 rows)'), (0, 1, 1, u'SEARCH TABLE feature USING INDEX fk_feature_msrun1 (msrun_msrun_id=?) (~2 rows)')
答案 0 :(得分:5)
你正在关联两个大表。一些快速数学:300k x 20k = 60亿行。如果只是返回所有这些行的问题,那么你肯定会受到I / O限制(但实际上只在( O )输出端)。但是,你的where子句几乎可以过滤所有内容,因为你只返回了10k行,所以你肯定会在这里绑定CPU。
SQLite一次不能使用多个索引,除了所谓的“OR optimizations”。此外,您不会通过内部联接获得任何性能提升,因为它们是“are converted into additional terms of the WHERE clause”。
最重要的是,SQLite将无法像postgresql
等人那样有效地执行您的查询。
我玩了你的场景,因为我很想知道你的查询可以优化多少。最终,似乎最好的优化是删除所有显式索引(!)。似乎SQLite会使用一些动态索引/索引来获得比我尝试的不同方法更好的性能。
作为演示,请考虑从您的模式派生的这个模式:
CREATE TABLE feature ( -- 300k
feature_id INTEGER PRIMARY KEY,
mzMin DOUBLE,
mzMax DOUBLE,
rtMin DOUBLE,
rtMax DOUBLE,
lnk_feature INT);
CREATE TABLE spectrum ( -- 20k
spectrum_id INTEGER PRIMARY KEY,
mz DOUBLE,
rt DOUBLE,
lnk_spectrum INT);
feature
有300k行,spectrum
20k(执行此操作的python代码位于下方)。由于定义INTEGER PRIMARY KEY
:
除了INTEGER PRIMARY KEY列外,还有UNIQUE和PRIMARY KEY 通过在数据库中创建索引来实现约束(in 与“CREATE UNIQUE INDEX”语句相同的方式。这样的 index与数据库中的任何其他索引一样用于优化 查询。结果,往往没有优势(但很重要 开销)在已经存在的一组列上创建索引 共同受制于UNIQUE或PRIMARY KEY约束。
使用上面的模式,SQLite提到它会在lnk_feature
的查询生命周期内创建一个索引:
sqlite> EXPLAIN QUERY PLAN SELECT feature_id, spectrum_id FROM spectrum, feature
...> WHERE lnk_feature = lnk_spectrum
...> AND rt >= rtMin AND rt <= rtMax
...> AND mz >= mzMin AND mz <= mzMax;
0|0|0|SCAN TABLE spectrum (~20000 rows)
0|1|1|SEARCH TABLE feature USING AUTOMATIC COVERING INDEX (lnk_feature=?) (~7 rows)
即使我使用该列或其他列上的索引进行测试,似乎运行该查询的最快方法是没有任何这些索引。
我使用python运行上述查询的速度最快是20分钟。这包括完成.fetchall()
。你提到在某些时候你会有150倍的行数。如果我是你,我会开始研究postgresql
; - )...请注意,您可以在线程中拆分工作,并可能通过可以运行的线程数来划分时间来完成查询同时(即通过可用的CPU数量)。
无论如何,这是我使用的代码。您可以自己运行它并报告查询在您的环境中运行的速度。请注意,我使用的是apsw
,因此如果您无法使用它,则需要调整以使用自己的sqlite3模块。
#!/usr/bin/python
import apsw, random as rand, time
def populate(cu):
cu.execute("""
CREATE TABLE feature ( -- 300k
feature_id INTEGER PRIMARY KEY,
mzMin DOUBLE, mzMax DOUBLE,
rtMin DOUBLE, rtMax DOUBLE,
lnk_feature INT);
CREATE TABLE spectrum ( -- 20k
spectrum_id INTEGER PRIMARY KEY,
mz DOUBLE, rt DOUBLE,
lnk_spectrum INT);""")
cu.execute("BEGIN")
for i in range(300000):
((mzMin, mzMax), (rtMin, rtMax)) = (get_min_max(), get_min_max())
cu.execute("INSERT INTO feature VALUES (NULL,%s,%s,%s,%s,%s)"
% (mzMin, mzMax, rtMin, rtMax, get_lnk()))
for i in range(20000):
cu.execute("INSERT INTO spectrum VALUES (NULL,%s,%s,%s)"
% (get_in_between(), get_in_between(), get_lnk()))
cu.execute("COMMIT")
cu.execute("ANALYZE")
def get_lnk():
return rand.randint(1, 2)
def get_min_max():
return sorted((rand.normalvariate(0.5, 0.004),
rand.normalvariate(0.5, 0.004)))
def get_in_between():
return rand.normalvariate(0.5, 0.49)
def select(cu):
sql = """
SELECT feature_id, spectrum_id FROM spectrum, feature
WHERE lnk_feature = lnk_spectrum
AND rt >= rtMin AND rt <= rtMax
AND mz >= mzMin AND mz <= mzMax"""
start = time.time()
cu.execute(sql)
print ("%s rows; %.2f seconds" % (len(cu.fetchall()), time.time() - start))
cu = apsw.Connection('foo.db').cursor()
populate(cu)
select(cu)
输出我得到:
54626 rows; 1210.96 seconds
答案 1 :(得分:2)
在sql部分做得更好。
总之,使用INDEXES !
答案 2 :(得分:1)
使用between而不是&gt; =和&lt; =进行范围比较。
self.cursor.execute("SELECT spectrum_id, feature_table_id "+
"FROM `spectrum` "+
"INNER JOIN `feature` "+
"ON feature.msrun_msrun_id = spectrum.msrun_msrun_id "+
"WHERE spectrum.scan_start_time between feature.rtMin " +
"AND feature.rtMax "+
"AND spectrum.base_peak_mz between feature.mzMin "+
"AND feature.mzMax")
您可以在spectrum.scan_start_time,feature.rtMin,feature.rtMax,spectrum.base_peak_mz,m feature.mzMin和feature.mzMax字段上创建非聚集索引。
答案 3 :(得分:1)
在正常的RDBMS中,应该在spectrum
和features
表之间进行散列连接。如果你可以强制它进行散列连接,那么查询应该会飞。
但是,您可以尝试单个查询吗?
self.cursor.execute("INSERT INTO `spectrum_has_feature` " +
"SELECT spectrum_id, feature_table_id "+
"FROM `spectrum` "+
"INNER JOIN `feature` "+
"ON feature.msrun_msrun_id = spectrum.msrun_msrun_id "+
"WHERE spectrum.scan_start_time >= feature.rtMin "+
"AND spectrum.scan_start_time <= feature.rtMax "+
"AND spectrum.base_peak_mz >= feature.mzMin "+
"AND spectrum.base_peak_mz <= feature.mzMax")
答案 4 :(得分:1)
我运行了Ludo的脚本,它在我的系统上报告了1451秒。然后我添加了以下索引,将时间减少到875秒(减少40%):
CREATE INDEX idx1 ON feature (lnk_feature, mzMin, mzMax, rtMin, rtMax);
仍然没有快速,但更好。这是EXPLAIN QUERY PLAN的输出:
0|0|0|SCAN TABLE spectrum (~20000 rows)
0|1|1|SEARCH TABLE feature USING COVERING INDEX idx1 (lnk_feature=? AND mzMin<?) (~7 rows)
请注意,这是一个覆盖索引,这意味着SQLite可以从索引中读取所需的所有字段,因此永远不必从功能表中读取。它还可以使用WHERE子句中五个条件中的两个条件的索引(而不是仅一个)。
答案 5 :(得分:0)
更改此
CREATE INDEX `fk_spectrum_msrun1` ON `spectrum` (`msrun_msrun_id` ASC);
其中一个(更具选择性)
CREATE INDEX `fk_spectrum_msrun1_1` ON `spectrum` (`msrun_msrun_id`, `scan_start_time`);
CREATE INDEX `fk_spectrum_msrun1_2` ON `spectrum` (`msrun_msrun_id`, `base_peak_mz`);
第一个可以加快scan_start_time
上的比较,第二个可以加快base_peak_mz
的比较。因为这些是不等式比较,所以两列上的索引都没用。
答案 6 :(得分:0)
1.使用而不是&lt; =或=&gt;。
2.在scan_start_time和base_peak_mz
上添加索引