【问题标题】:Why is my SQL statement so slow (sqlite 3)?为什么我的 SQL 语句这么慢(sqlite 3)?
【发布时间】:2021-11-07 12:12:09
【问题描述】:

所以我使用 python sqlite3 连接到笔记本电脑上本地保存的数据库并查询表。 这些表相当大,有多达 500 万行(按分钟计算的外汇汇率)。

我在数据库中有 3 个相关表(“EURUSD”、“BTCUSD”、“ETHUSD”),每个表都有“Timestamp”和“Open”列,对应于特定分钟的汇率(格式为 @ 987654321@.

我在 python whick 中有另一个表,如下所示: Table to be filled with values

我想要做的是以下几点: 对于要填充的表中的每一行:检查货币并在数据库中搜索 NEAREST 时间戳值并返回相应的“Open”。所以如果表中的货币是“欧元”。转到表“EURUSD”,选择最近的时间戳并返回开盘价。如果币种为“BTC”,则进入表“BTCUSD”,选择最近的时间戳,返回open。

它必须是最近的时间戳,因为确切的时间戳可能不在数据库中。

我当前的代码看起来像这样,但我要为每一行迭代这个代码 100 次,这让它变慢了。

conn = sqlite3.connect(path)
cur = conn.cursor()
for idx in dataframe.index:
  timestamp = dataframe.loc[idx, "Timestamp"]
  cur.execute("SELECT TimestampUnix, Open FROM EURUSD WHERE EURUSD.TimestampUnix< "+str(timestamp)
  ORDER BY EURUSD.TimestampUnix DESC LIMIT 1"
  query = cur.fetchall()
  rate = query[0][1]
  dataframe.loc[idx, "ForexRate"] = rate

我在“TimestampUnix”上为表建立了索引,这将一个查询的运行时间从 5 秒降低到了 1 秒。但是,它似乎仍然渴望本地执行的查询。

我的想法是 SQL 查询可能是问题所在,因为在某些情况下它几乎选择了整个表并且必须对其进行排序。 您还有其他想法可以让它更快吗?

编辑:我编辑了我想做的事情的描述。我认为(正如你们中的一些人已经提到的那样)我的解决方案完全低效,也许这可以通过单个查询来解决。查询本质上应该做两件事:

  • 选择要搜索的正确表格
  • 找到最近的时间戳并返回对应的打开

由于我不精通 SQL,我在这方面遇到了麻烦,希望能得到帮助。

【问题讨论】:

  • 通常,当过滤列上没有索引时,像这样的简单 SQL 查询可能会被 WHERE 子句减慢,特定列名上的 SELECT 也是 goog 的做法。
  • 我在 TimestampUnix 列上有一个索引,它是 WHERE 子句中的过滤列。在特定列名上选择 SELECT 是什么意思?它看起来和我的代码不同吗?
  • 在 sqlite3 命令行 shell 中打开数据库并使用 .expert command 查看是否有更好的索引可以添加(可能需要降序)
  • 您还可以在执行此操作时更新您的问题以包含EXPLAIN QUERY PLAN ... 输出。
  • 最后,将您要查找的时间戳值绑定到查询中的一个参数,而不是每次都构建一个新的查询字符串。 bobby-tables.com/python

标签: python sql performance sqlite


【解决方案1】:

这是根据问题中的新细节更新的回复。可以在执行以下操作之前将基于应用程序的表插入到数据库中,或者如果需要,可以通过 CTE 术语注入数据。

测试用例:

Working test case for sqlite

感兴趣的 SQL,首先是 SELECT 以生成应用程序可以用来更新本地数据的结果,然后是更新在数据库中创建的目标表的示例,其中包含要填充的应用程序数据新的打开/评分数据。

文字3000000000 只是我们从提供的应用程序时间戳中搜索Open 详细信息的时间戳增量/范围,对于应用程序提供的每一行。基本上,我们从EURUSD 表中找到最近的先前时间戳。

-- Just calculate the result, without touching target
WITH RECURSIVE ts (ts, n) AS (
         SELECT TimestampUnix, ROW_NUMBER() OVER (ORDER BY TimestampUnix)
           FROM target WHERE currency = 'EUR'
     )
   , xrows (ts, n, TimestampUnix, Open, rn) AS (
         SELECT ts, n, EURUSD.TimestampUnix, EURUSD.Open
              , ROW_NUMBER() OVER (PARTITION BY n ORDER BY TimestampUnix DESC)
           FROM ts
           JOIN EURUSD
             ON EURUSD.TimestampUnix <=  ts.ts
            AND EURUSD.TimestampUnix >   ts.ts - 3000000000
     )
SELECT *
  FROM xrows WHERE rn = 1
;
-- Now use the above to UPDATE the target table.
-- sqlite might not support a JOIN here.
-- So we use correlated behavior in the SET clause

UPDATE target
   SET rate = (
            WITH ts (ts, n, currency) AS (
                     SELECT TimestampUnix, ROW_NUMBER() OVER (ORDER BY TimestampUnix)
                          , currency
                       FROM target WHERE currency = 'EUR'
                 )
               , xrows (ts, n, currency, TimestampUnix, Open, rn) AS (
                     SELECT ts, n, currency, EURUSD.TimestampUnix, EURUSD.Open
                          , ROW_NUMBER() OVER (PARTITION BY n ORDER BY TimestampUnix DESC)
                       FROM ts
                       JOIN EURUSD
                         ON EURUSD.TimestampUnix <=  ts.ts
                        AND EURUSD.TimestampUnix >   ts.ts - 3000000000
                 )
            SELECT Open
              FROM xrows
             WHERE rn = 1
               AND ts       = target.TimestampUnix
               AND currency = target.currency
       )
 WHERE currency = 'EUR'
;

eurusd 表和一些生成的示例数据:

CREATE TABLE eurusd (
     TimestampUnix  timestamp, Open  int
);

-- Add some sample data for testing...
INSERT INTO eurusd
WITH RECURSIVE cte (ts, Open, n) AS (
        SELECT 1630942385000, 1101, 1 UNION ALL
        SELECT ts-1000000000, Open-1, n+1 FROM cte
         WHERE n < 15
     )
SELECT ts, Open FROM cte
;

生成的数据如下所示:

目标/应用表:

-- Let this table represent the data in the application to be updated.
CREATE TABLE target (
     TimestampUnix  timestamp
   , currency       varchar(10)
   , rate           int
);

-- The application could insert these rows before processing, or just provide this
-- data via a CTE term.
INSERT INTO target VALUES
    (1630942385000, 'TST',    0)
  , (1630942377000, 'EUR', NULL)
  , (1630942162000, 'BTC', NULL)
  , (1625942399000, 'EUR', NULL)
;

这张桌子看起来像这样。我们希望更新突出显示的行:

第一个查询的结果,只显示生成的与找到的最接近的rate 的行:

UPDATE 之后的target 表的结果,更新后的rates 突出显示:

原始回复: 这是计算感兴趣的时间戳的一种方法(从 current_timestamp 开始每 -12 小时),然后根据问题中的逻辑返回每个时间戳 1 行。

我们从 current_timestamp - 12 小时开始,并迭代(通过递归)产生连续的时间戳值,从中计算下一个结果,直到某个停止条件。我添加了一个条件,将迭代次数限制在 10 次左右,但只有几天的数据,即 4 x 12 小时范围。

ROW_NUMBER 用于计算每个时间戳范围内每个 EURUSD 行的位置(降序),因此 rn = 1 表示该范围内最近的行,类似于您的ORDER BY ts DESC LIMIT 1

我还调整了您的逻辑以避免搜索给定时间戳之前的所有数据。如果其中包含数月或数年的数据,您可能只需要查看最后一天左右,即可获得每个时间戳的最新 Open 值。如果这是可以接受的,这也可以帮助您当前的 SQL。

通过不迭代 SQL 100 次,您也可能会获得更显着的改进。

更新为更好的形式:

WITH RECURSIVE ts (ts, n) AS (
         SELECT datetime(current_timestamp, '-12 hour'), 1
          UNION ALL
         SELECT datetime(ts, '-12 hour'), n+1
           FROM ts
          WHERE n < 10
     )
   , xrows (ts, n, TimestampUnix, Open, rn) AS (
         SELECT ts, n, EURUSD.TimestampUnix, EURUSD.Open
              , ROW_NUMBER() OVER (PARTITION BY n ORDER BY TimestampUnix DESC)
           FROM ts
           JOIN EURUSD
             ON EURUSD.TimestampUnix <  datetime(ts.ts, '+12 hour')
            AND EURUSD.TimestampUnix >= ts.ts
     )
SELECT *
  FROM xrows WHERE rn = 1
;

这只是一个例子。以下小提琴包含一些用于测试的数据:

Working Test Case with data

使用的数据:

基于该数据的结果:

【讨论】:

  • 感谢您的回答。我编辑了我的问题并为我想要做的事情提供了更多背景信息,因为您正确地提到了查询的迭代是导致它变慢的原因。
  • @Pat396 这更好,因为我们在数据库中有感兴趣的时间戳列表。现在的问题是,您是否有某个范围可以找到最近的时间戳?例如,我们是否能够在前一天或前一周内找到ts?任何限制都可能对整体性能有很大帮助。我们能否将要更新的表中的时间戳限制为仅包含当前包含 NULL 的时间戳?
  • @Pat396 哦。 “python中的表” ...您可以将该表插入数据库(然后更新它)或将该表添加到SQL(派生或作为CTE术语)。任何一个都可能比分别为每个项目迭代 python 列表更好。
  • 大多数情况下,距离最近的时间戳只有几分钟的路程。但可能会有长达 5 天的极端情况。此外,查询可能仅限于更新当前包含 NULL 的那些行。是的,我也在考虑将表插入数据库,然后对其进行更新并将其返回给 python 进行进一步处理,所以这是可能的。
  • @Pat396 我有一个小测试用例,使用原始时间戳(整数值)来避免示例中的任何日期转换并发症。这可以一次对所有货币类型进行。但我现在只关注“欧元”案例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-20
  • 1970-01-01
  • 1970-01-01
  • 2018-02-24
  • 2012-04-30
相关资源
最近更新 更多