5

我问了两个相关问题(如何在运行 sqlite 查询后加快获取结果的速度?以及sqlite.fetchall() 这么慢是否正常?)。我已经改变了一些东西并得到了一些加速,但是 select 语句仍然需要一个多小时才能完成。

我有一个feature包含rtMinrtMax和值mzMin的表。mzMax这些值一起是矩形的角(如果您阅读我的旧问题,我将这些值分开保存,而不是从convexhull表中获取 min() 和 max(),这样可以更快地工作)。
我得到了一个spectrum带有一个rt和一个mz值的表。我有一个表格,当光谱的rtmz值在特征的矩形中时,它将特征链接到光谱。

为此,我使用以下 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 个 msruns。结果是 10822 个匹配项。


更新 3:

使用新索引 (CREATE INDEX fk_spectrum_msrun1_2ON spectrum( msrun_msrun_id, base_peak_mz);) 之间保存约 20 秒:查询耗时:76.4599349499 秒 5864.15418601 秒自 fetchall


更新 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)') 
4

7 回答 7

6

您正在关联两个大表。一些快速的数学运算:300k x 20k = 60 亿行。如果只是返回所有这些行的问题,那么您肯定会受到 I/O 限制(但实际上仅在 ( O ) 输出端)。但是,您的 where 子句几乎过滤掉了所有内容,因为您只返回了 10k 行,因此您可以肯定这里的 CPU 受限。

SQLite 一次不能使用多个索引,除了所谓的“ OR 优化”。此外,您不会从内部连接中获得任何性能提升,因为它们“被转换为 WHERE 子句的附加条款”。

底线是 SQLite 将无法像 say postgresqlet al 那样有效地执行您的查询。

我玩弄了您的场景,因为我很想知道您的查询可以优化多少。最终,似乎最好的优化是删除所有显式索引(!)。似乎 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 行和spectrum20k 行(执行此操作的 python 代码在下面的某处)。由于定义,没有指定显式索引,只有隐INTEGER PRIMARY KEY式索引:

除了 INTEGER PRIMARY KEY 列之外,UNIQUE 和 PRIMARY KEY 约束都是通过在数据库中创建索引来实现的(与“CREATE UNIQUE 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
于 2012-05-05T16:36:57.870 回答
2

让它在 sql 部分更好。

总之,使用 INDEXES

于 2012-05-04T08:37:20.573 回答
1

使用 between 代替 >= 和 <= 进行范围比较。

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 字段上创建非聚集索引。

于 2012-05-04T08:36:39.410 回答
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 子句中五个条件中的两个(而不是仅一个)。

于 2012-09-16T22:09:30.040 回答
1

在普通的 RDBMS 中,应该在spectrumfeatures表之间进行哈希连接。如果您可以强制它执行散列连接,则查询应该会飞。

但是,您可以尝试单个查询吗?

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")  
于 2012-05-04T09:06:38.830 回答
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。因为这些是不等式比较,所以两列上的索引都没有用。

于 2012-05-04T09:56:35.307 回答
0

1.使用 between 代替 <= 或 =>。

2.在scan_start_time和base_peak_mz上添加索引

于 2012-05-04T10:39:11.537 回答