【问题标题】:Is it possible to optimize these SQL queries further?是否可以进一步优化这些 SQL 查询?
【发布时间】:2011-06-18 18:54:51
【问题描述】:

我有一个 Rails 应用程序(在 Heroku 帐户上运行),它正在为主页获取一堆关于符合特定条件的记录数量的统计信息。每个计数在页面上显示为一个数字。我的表(列表)包含大约 22,500 条记录。在生产环境中,加载页面大约需要 350 毫秒(仍然低于阈值,但对于主页来说不是很好)。

请忍受这里的查询数量,我想描述我正在尝试做的事情的冗余。这感觉可以更有效地完成。有什么想法吗?

SELECT COUNT(1) FROM listings WHERE (city in ('Syracuse'))
SELECT COUNT(1) FROM listings WHERE (city in ('Syracuse')) AND (created_at >= '2011-01-30 18:28:44.656702')
SELECT COUNT(1) FROM listings WHERE (city in ('Cicero', 'Clay', 'Lysander', 'VanBuren', 'Salina'))
SELECT COUNT(1) FROM listings WHERE (city in ('Cicero', 'Clay', 'Lysander', 'VanBuren', 'Salina')) AND (created_at >= '2011-01-30 18:28:44.811090')
SELECT COUNT(1) FROM listings WHERE (city in ('DeWitt', 'Manlius', 'Pompey'))
SELECT COUNT(1) FROM listings WHERE (city in ('DeWitt', 'Manlius', 'Pompey')) AND (created_at >= '2011-01-30 18:28:44.954442')
SELECT COUNT(1) FROM listings WHERE (city in ('Onondaga', 'Elbridge', 'Geddes', 'Camillus'))
SELECT COUNT(1) FROM listings WHERE (city in ('Onondaga', 'Elbridge', 'Geddes', 'Camillus')) AND (created_at >= '2011-01-30 18:28:45.105438')
SELECT COUNT(1) FROM listings WHERE (city in ('Fabius', 'Lafayette', 'Marcellus', 'Otisco', 'Skaneateles', 'Spafford', 'Tully'))
SELECT COUNT(1) FROM listings WHERE (city in ('Fabius', 'Lafayette', 'Marcellus', 'Otisco', 'Skaneateles', 'Spafford', 'Tully')) AND (created_at >= '2011-01-30 18:28:45.258860')
SELECT COUNT(1) FROM listings WHERE (city in ('West Monroe', 'Hastings', 'Constantia', 'Palermo', 'Mexico', 'Parish', 'Schroeppel'))
SELECT COUNT(1) FROM listings WHERE (city in ('West Monroe', 'Hastings', 'Constantia', 'Palermo', 'Mexico', 'Parish', 'Schroeppel')) AND (created_at >= '2011-01-30 18:28:45.411138') 

我考虑的一个选项是在我的列表模型上使用 after_add 和 after_remove 挂钩来使用这些统计信息更新单独的表。我唯一关心的是涉及的维护问题。但是,新列表只会在一天中添加几次,因此更新所述表本身不会导致性能问题。

谢谢!

【问题讨论】:

  • 它默认使用 Heroku 提供的 Postgres 数据库。我也在使用免费的 Heroku 帐户(可能是罪魁祸首?)。

标签: sql ruby-on-rails ruby optimization


【解决方案1】:

各种方法,并非都是面向数据库的。

您可以将所有选择组合成一个查询,如下所示:

SELECT COUNT(CASE WHEN city = 'Syracuse' THEN 1 END) as syracuse,
       COUNT(CASE WHEN city = 'Syracuse' AND created_at >= '2011-01-30 18:28:44.656702' THEN 1 END) as syracuse_recent,
       /* etc... */
FROM listings

这将只是对表格进行一次扫描以收集所有统计信息。

或者/另外,将从数据库中提取的统计信息缓存在应用程序的内存中,或者使用类似 memcached 的东西。如果不需要最新的准确统计信息,这会在初始填充后完全从数据库中卸载查询。

【讨论】:

  • 无需填写我的答案,但我将建议两个查询,一个带有日期过滤条件,一个没有。然后可能有两个索引调整到两个查询。
  • 如果所有查询最终都将覆盖所有表(这似乎很可能),那么使用索引不太可能带来好处。尤其是在 PostgreSQL 中,无论如何都必须引用堆数据。
  • 感谢您的回答。这是否开始让 CASE 语句变得有点过于依赖数据库? CASE 语句是否适用于所有数据库?
  • 该语法肯定适用于 PostgreSQL、SQL Server 和 Oracle; CASE 是 ANSI 标准构造,用于替换更多特定于 db 的函数,如 decode()nullif() 等。像这样使用sum/count(case...) 是一种广泛使用的技术。
【解决方案2】:

首先,您应该检查表上有哪些索引(尝试添加和删除单个字段的索引以及双向的复合索引)。

还要确保准确分析 350ms 的组成部分(使用 firebug 或 YSlow 之类的东西)。

最后,如果你真的有很少的更新并且你想维护一个汇总表钩子不是唯一的方法 - 你也可以编写触发器来为你完成这项工作。

【讨论】:

    【解决方案3】:

    就个人而言,我会添加两个新表,一个包含城市组,另一个包含组和城市之间的多对多链接表。您将需要“city_group_id”、“city_group_name”、“dt_count_threshold”。第二个表是“city_group_id”、“city_id”。然后您可以对多对多链接表执行选择,并根据您的日期/时间限制加入城市表。

    -- unrestricted count
    selec cg.city_group_name, count(*) as cnt
    from dbo.city_group cg
    join dbo.city_group_city cgc on cg.city_group_id = cgc.city_group_id
    group by city_group_name
    
    -- restricted
    selec cg.city_group_name, count(*) as cnt
    from dbo.city_group cg
    join dbo.city_group_city cgc on cg.city_group_id = cgc.city_group_id
    join dbo.city c on c.city_id = cgc.city_id
    group by city_group_name
    where c.created_at >= cg.dt_count_threshold
    

    请注意,这些是未经测试的查询,因此可能需要进行一些小的调整。并确保所有索引都设置正确以避免表扫描。

    【讨论】:

      猜你喜欢
      • 2023-04-06
      • 1970-01-01
      • 2011-04-25
      • 2022-12-05
      • 1970-01-01
      • 2018-01-11
      • 2022-10-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多