【问题标题】:Fast complex SQL queries on PostgreSQL database with Python使用 Python 对 PostgreSQL 数据库进行快速复杂的 SQL 查询
【发布时间】:2017-10-07 12:31:44
【问题描述】:

我在 PostgreSQL 数据库中有一个包含 +50M 记录的大型数据集,需要大量计算、内连接。

PythonPsycopg2 的首选工具。

使用fetchmany 的 20,000 条记录运行该过程需要几个小时才能完成。

需要按顺序执行,因为需要单独获取 50M 的每条记录,然后需要运行另一个查询(在下面的示例中),然后才能返回结果并保存在单独的表中。

在每个表(总共 5 个表)上正确配置了索引,并且复杂查询(返回计算值 - 示例如下)大约需要 240MS 来返回结果(当数据库不在加载)。

Celery 用于在单独的表中处理计算值的数据库插入。

我的问题是关于减少整体运行时间和更快产生结果/计算的常用策略。 换句话说,什么是一个有效的方法来遍历所有记录,通过第二个查询计算一个字段的值然后保存结果。

更新:

在试图混淆敏感细节时,我无意中错过了一条重要信息。很抱歉。

原始SELECT 查询计算从不同表聚合的值如下:

SELECT CR.gg, (AX.b + BF.f)/CR.d AS calculated_field
FROM table_one CR
  LEFT JOIN table_two AX ON EX.x = CR.x
  LEFT JOIN table_three BF ON BF.x = CR.x
WHERE CR.gg = '123'
GROUP BY CR.gg;

PS:SQL 查询是由我们经验丰富的 DBA 编写的,所以我相信它已经过优化。

【问题讨论】:

  • 在 psql 中运行相同的查询 naked,以估计传入/传出 pyhon 前端所需的时间,以及将数据转换为/的时间来自python结构。
  • ... and a single query takes around ... 什么是单一查询 ??
  • @wildplasser 每次查询数据集时的单个查询(包括连接命令),即:select * from table_one er left join table_two ex ON ex.x = er.y left join table_three ef on ef.x = er.z where er.gg = '123' group by er.gg;
  • 还有:什么是不是 单一查询 ???注意:group by er.gg 没有用,因为只有一组(并且没有聚合)
  • 好吧:不要那样做! (这将花费两个往返数据库 + 解析+执行 per item )相反,让数据库计算 all 的值 all您需要的记录,并在一次扫描中获取所有结果。

标签: python database postgresql performance pandas


【解决方案1】:
  • 不要循环记录并为每条记录重复调用 DBMS。
  • 相反,让 DBMS 处理大块(最好:全部)数据
  • 然后,让它吐出所有结果。

下面是我的 twitter-sucker 的 sn-p(带有相当复杂的 ugly 查询)


def fetch_referred_tweets(self):
    self.curs = self.conn.cursor()
    tups = ()
    selrefd = """SELECT twx.id, twx.in_reply_to_id, twx.seq, twx.created_at
    FROM(
     SELECT tw1.id, tw1.in_reply_to_id, tw1.seq, tw1.created_at
     FROM tt_tweets tw1
     WHERE 1=1
      AND tw1.in_reply_to_id > 0
      AND tw1.is_retweet = False
      AND tw1.did_resolve = False
      AND NOT EXISTS ( SELECT * FROM tweets nx
                       WHERE nx.id = tw1.in_reply_to_id)
      AND NOT EXISTS ( SELECT * FROM tt_tweets nx
                       WHERE nx.id = tw1.in_reply_to_id)
     UNION ALL
    SELECT tw2.id, tw2.in_reply_to_id, tw2.seq, tw2.created_at
     FROM tweets tw2
     WHERE 1=1
      AND tw2.in_reply_to_id > 0
      AND tw2.is_retweet = False
      AND tw2.did_resolve = False
      AND NOT EXISTS ( SELECT * FROM tweets nx
                       WHERE nx.id = tw2.in_reply_to_id)
      AND NOT EXISTS ( SELECT * FROM tt_tweets nx
                       WHERE nx.id = tw2.in_reply_to_id)
     -- ORDER BY tw2.created_at DESC
    )twx
     LIMIT %s;"""
      # -- AND tw.created_at < now() - '15 min':: interval
      # -- AND tw.created_at >= now() - '72 hour':: interval

    count = 0
    uniqs = 0
    self.curs.execute(selrefd, (quotum_referred_tweets, ) )
    tups = self.curs.fetchmany(quotum_referred_tweets)
    for tup in tups:
      if tup == None: break
      print ('%d -->> %d [seq=%d] datum=%s' % tup)
      self.resolve_list.append(tup[0] ) # this tweet
      if tup[1] not in self.refetch_tweets:
          self.refetch_tweets[ tup[1] ] = [ tup[0]] # referred tweet
          uniqs += 1

      count += 1

    self.curs.close()

注意:您的查询没有意义:

  • 您只能从ertable 中选择字段
  • 所以,两个LEFT JOINed 表可以省略
  • 如果exef 包含多个匹配的行,结果集可能 大于从er 中选择的所有行,导致重复er 记录
  • 存在GROUP BY,但选择列表中没有聚合

select er.gg, er.z, er.y
from table_one er
where er.gg = '123'
-- or:
where er.gg >= '123'
  and er.gg <= '456'
ORDER BY er.gg, er.z, er.y -- Or: some other ordering
    ;

【讨论】:

  • 感谢您的详尽回答。您的观点完全有道理,而不是关于查询的观点,因为我犯了一个错误并添加了更新来澄清问题。另外,感谢代码 sn-p。
  • 注意:范围内没有ez. 别名。请发布 real 查询(加上表定义等。plonk?
【解决方案2】:

由于您要在查询中进行连接,逻辑上要做的事情是解决它,这意味着创建所谓的汇总表,这个汇总表 - 驻留在数据库中 - 将保存最终的连接数据集,所以在您的 python 代码中,您只需从中获取/选择数据。

另一种方法是使用物化视图link

【讨论】:

    【解决方案3】:

    我接受了@wildplasser 的建议,并将计算操作作为函数移到了数据库中。 至少可以说,结果非常高效,总运行时间降至几分钟/~ 小时。

    回顾一下:

    • 不再按顺序获取数据库记录 前面提到过
    • 通过函数PostgreSQL function在数据库内部进行计算

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-13
      • 2011-09-29
      • 2021-08-13
      • 1970-01-01
      • 2011-03-22
      • 1970-01-01
      • 2022-01-17
      • 2022-01-21
      相关资源
      最近更新 更多