Postgresql 使用多连接和分组优化SQL查询(Postgres 9.3)

Postgresql 使用多连接和分组优化SQL查询(Postgres 9.3),postgresql,join,postgresql-9.3,postgresql-performance,sql-optimization,Postgresql,Join,Postgresql 9.3,Postgresql Performance,Sql Optimization,我浏览了其他一些帖子,设法让我的查询运行得更快一些。但是,对于如何进一步优化此查询,我感到困惑。我将在一个网站上使用它,当页面加载时它将执行查询,但是5.5秒太长了,不能等待更简单的东西。最大的表约有4000000行,其他表各有400000行 表格结构 匹配 id BIGINT PRIMARY KEY, region TEXT, matchType TEXT, matchVersion TEXT 团队 matchid BIGINT REFERENCES match(id), id INTEGE

我浏览了其他一些帖子,设法让我的查询运行得更快一些。但是,对于如何进一步优化此查询,我感到困惑。我将在一个网站上使用它,当页面加载时它将执行查询,但是5.5秒太长了,不能等待更简单的东西。最大的表约有4000000行,其他表各有400000行

表格结构

匹配

id BIGINT PRIMARY KEY,
region TEXT,
matchType TEXT,
matchVersion TEXT
团队

matchid BIGINT REFERENCES match(id),
id INTEGER,
PRIMARY KEY(matchid, id),
winner TEXT
id INTEGER PRIMARY KEY,
name TEXT
冠军

id INTEGER PRIMARY KEY,
version TEXT,
name TEXT
项目

matchid BIGINT REFERENCES match(id),
id INTEGER,
PRIMARY KEY(matchid, id),
winner TEXT
id INTEGER PRIMARY KEY,
name TEXT
参与者

PRIMARY KEY(matchid, id),
id INTEGER NOT NULL,
matchid BIGINT REFERENCES match(id),
championid INTEGER REFERENCES champion(id),
teamid INTEGER,
FOREIGN KEY (matchid, teamid) REFERENCES team(matchid, id),
magicDamageDealtToChampions REAL,
damageDealtToChampions REAL,
item0 TEXT,
item1 TEXT,
item2 TEXT,
item3 TEXT,
item4 TEXT,
item5 TEXT,
highestAchievedSeasonTier TEXT
查询

select champion.name,
sum(case when participant.item0 = '3285' then 1::int8 else 0::int8 end) as it0,
sum(case when participant.item1 = '3285' then 1::int8 else 0::int8 end) as it1,
sum(case when participant.item2 = '3285' then 1::int8 else 0::int8 end) as it2,
sum(case when participant.item3 = '3285' then 1::int8 else 0::int8 end) as it3,
sum(case when participant.item4 = '3285' then 1::int8 else 0::int8 end) as it4,
sum(case when participant.item5 = '3285' then 1::int8 else 0::int8 end) as it5
from participant
left join champion
on champion.id = participant.championid
left join team
on team.matchid = participant.matchid and team.id = participant.teamid
left join match
on match.id = participant.matchid
where (team.winner = 'True' and matchversion = '5.14'  and matchtype='RANKED_SOLO_5x5')
group by champion.name;
解释分析的输出

我到目前为止所做的事情

我在
match.region
participant.championid
上创建了单独的索引,并在team
上创建了部分索引,其中winner='True'
(因为这只是我感兴趣的内容)。请注意,
enable_seqscan=on
。基本上,我想要得到的结果是这样的:

Champion   |item0 | item1 | ... | item5
champ_name | num  |  num1 | ... | num5
...
由于我还是数据库设计的初学者,如果我的整个表设计中存在缺陷,我不会感到惊讶。不过,我仍然倾向于认为这个查询绝对没有效率。我已经玩过内连接和左连接,但是没有明显的区别。此外,match需要是
bigint
(或者大于
整数的值,因为它太小了)。

我会尝试使用 将(*)过滤器(其中item0='3285')计数为it0

为了你的计数而不是总数

还有,为什么要加入最后两个表,然后使用where语句呢。这与目的背道而驰,常规的内部连接速度更快

select champion.name,
count(*) filter( where participant.item0 = 3285) as it0,
count(*) filter( where participant.item1 = 3285) as it1,
count(*) filter( where participant.item2 = 3285) as it2,
count(*) filter( where participant.item3 = 3285) as it3,
count(*) filter( where participant.item4 = 3285) as it4,
count(*) filter( where participant.item5 = 3285) as it5
from participant
join champion on champion.id = participant.championid
join team on team.matchid = participant.matchid and team.id = participant.teamid
join match on match.id = participant.matchid
where (team.winner = 'True' and matchversion = '5.14'  and matchtype='RANKED_SOLO_5x5')
group by champion.name;
数据库设计 我建议:

CREATE TABLE matchversion (
  matchversion_id int PRIMARY KEY
, matchversion    text UNIQUE NOT NULL
);

CREATE TABLE matchtype (
  matchtype_id int PRIMARY KEY
, matchtype    text UNIQUE NOT NULL
);

CREATE TABLE region (
  region_id int PRIMARY KEY
, region    text NOT NULL
);

CREATE TABLE match (
  match_id        bigint PRIMARY KEY
, region_id       int REFERENCES region
, matchtype_id    int REFERENCES matchtype
, matchversion_id int REFERENCES matchversion
);

CREATE TABLE team (
  match_id bigint REFERENCES match
, team_id  integer  -- better name !
, winner   boolean  -- ?!
, PRIMARY KEY(match_id, team_id)
);

CREATE TABLE champion (
  champion_id int PRIMARY KEY
, version     text
, name        text
);

CREATE TABLE participant (
  participant_id serial PRIMARY KEY -- use proper name !
, champion_id    int NOT NULL REFERENCES champion
, match_id       bigint NOT NULL REFERENCES match -- this FK might be redundant
, team_id        int
, magic_damage_dealt_to_champions real
, damage_dealt_to_champions       real
, item0      text  -- or integer ??
, item1      text
, item2      text
, item3      text
, item4      text
, item5      text
, highest_achieved_season_tier text  -- integer ??
, FOREIGN KEY (match_id, team_id) REFERENCES team
);
  • 为了获得更小的表和索引以及更快的访问,需要进行更多的规范化。为
    匹配版本
    匹配类型
    区域
    创建查找表,并仅在
    匹配
    中写入一个小整数ID

  • 看起来像是列
    participant.item0
    item5
    HighestAceivedSeasonTier
    可以是
    整数
    ,但定义为
    文本

  • team.winner
    似乎是
    boolean
    ,但定义为
    text

  • 我还更改了列的顺序以提高效率。详情:

查询 基于上述修改和Postgres 9.3:

SELECT c.name, *
FROM  (
   SELECT p.champion_id
        , count(p.item0 = '3285' OR NULL) AS it0
        , count(p.item1 = '3285' OR NULL) AS it1
        , count(p.item2 = '3285' OR NULL) AS it2
        , count(p.item3 = '3285' OR NULL) AS it3
        , count(p.item4 = '3285' OR NULL) AS it4
        , count(p.item5 = '3285' OR NULL) AS it5
   FROM   matchversion   mv  
   CROSS  JOIN matchtype mt
   JOIN   match          m  USING (matchtype_id, matchversion_id)
   JOIN   team           t  USING (match_id)
   JOIN   participant    p  USING (match_id, team_id)
   WHERE  mv.matchversion = '5.14'
   AND    mt.matchtype = 'RANKED_SOLO_5x5'
   AND    t.winner = 'True' -- should be boolean
   GROUP  BY p.champion_id
   ) p
JOIN  champion c USING (champion_id);  -- probably just JOIN ?
  • 由于
    champion.name
    没有定义
    UNIQUE
    ,因此
    按它分组可能是错误的。这也是低效的。改为使用
    participant.championid
    (如果需要结果中的名称,请稍后加入
    champion

  • LEFT JOIN
    的所有实例都是无意义的,因为左侧表上仍然有谓词和/或使用
    groupby
    中的列

  • -ed
    周围的括号,其中不需要
    条件

  • 在Postgres 9.4或更高版本中,您可以使用新的聚合
    过滤器
    语法。详情和备选方案:

指数 您已经拥有的
团队
上的部分索引应如下所示,以便只允许索引扫描:

CREATE INDEX on team (matchid, id) WHERE winner -- boolean
但是从我看到的情况来看,您可能只是在
participant
中添加一个
winner
列,然后完全删除表
team
(除非还有更多)

此外,该索引也不会有多大帮助,因为(从您的查询计划中可以看出)该表有800k行,其中一半符合条件:

当您有更多不同的匹配类型和匹配版本时,
match
上的此索引将(稍后)提供更多帮助:

CREATE INDEX on match (matchtype_id, matchversion_id, match_id);
尽管如此,虽然400k行中有100k行符合条件,但该索引仅适用于仅索引扫描。否则,顺序扫描会更快。一个索引通常只需选择表的5%或更少

您的主要问题是您运行的测试用例显然是几乎不现实的数据分布。有了更具选择性的谓词,索引将更容易使用

在一边 确保你有


enable_seqscan=on
不用说。这只能在调试时关闭,或者作为最后手段在本地关闭。

整个计数(*where…)只适用于后期的postgres版本。为了获得最大的速度,您还可以对表进行集群。这适用于哪个版本?似乎9点都不管用。3@andrewjdg:这不适用于Postgres的任何版本。无效语法。抱歉,请检查新更新的查询。在我的头顶上打字,关于过滤器语法是错误的。应该在9.4上工作。对——完全忘记了!我使用的是9.3版