我问了两个相关问题(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 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);
更新2:
我有 20938 个光谱、305742 个特征和 2 个 msrun。结果是 10822 场比赛。
更新 3:
使用新索引(CREATE INDEX fk_spectrum_msrun1_2
ON spectrum
(msrun_msrun_id
, base_peak_mz
);)和之间节省大约 20 秒:
查询耗时:76.4599349499 秒
自 fetchall 以来 5864.15418601 秒
更新 4:
从 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)')
最佳答案
您正在关联两个大表。一些快速数学: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 代码在下面某处)。没有指定明确的索引,only implicit ones由于 INTEGER PRIMARY KEY
的定义:
INTEGER PRIMARY KEY columns aside, both UNIQUE and PRIMARY KEY constraints are implemented by creating an index in the database (in the same way as a "CREATE UNIQUE INDEX" statement would). Such an index is used like any other index in the database to optimize queries. As a result, there often no advantage (but significant overhead) in creating an index on a set of columns that are already collectively subject to a UNIQUE or PRIMARY KEY constraint.
使用上面的模式,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
关于python - 选择查询非常慢,我怎样才能加快速度?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10445392/