and*_*jdg 2 postgresql join sql-optimization postgresql-performance postgresql-9.3
我浏览了一些其他帖子并设法使我的查询运行得更快。但是,我对如何进一步优化此查询感到茫然。我将在一个网站上使用它,它会在页面加载时执行查询,但是 5.5 秒对于等待应该更简单的东西来说太长了。最大的表大约有 4,000,000 行,其他的每行大约有 400,000。
表结构
比赛
id BIGINT PRIMARY KEY,
region TEXT,
matchType TEXT,
matchVersion TEXT
Run Code Online (Sandbox Code Playgroud)
团队
matchid BIGINT REFERENCES match(id),
id INTEGER,
PRIMARY KEY(matchid, id),
winner TEXT
Run Code Online (Sandbox Code Playgroud)
冠军
id INTEGER PRIMARY KEY,
version TEXT,
name TEXT
Run Code Online (Sandbox Code Playgroud)
物品
id INTEGER PRIMARY KEY,
name TEXT
Run Code Online (Sandbox Code Playgroud)
参与者
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
Run Code Online (Sandbox Code Playgroud)
询问
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;
Run Code Online (Sandbox Code Playgroud)
输出EXPLAIN ANALYZE:http : //explain.depesz.com/s/ZYX
到目前为止我所做的
我创建了单独的索引match.region,participant.championid以及部分指数队where winner = 'True'(因为只有我感兴趣的是)。请注意,enable_seqscan = on因为当它关闭时,查询非常慢。本质上,我想要得到的结果是这样的:
Champion |item0 | item1 | ... | item5
champ_name | num | num1 | ... | num5
...
Run Code Online (Sandbox Code Playgroud)
由于我在数据库设计方面仍然是初学者,如果我的整体表设计存在缺陷,我不会感到惊讶。不过,我仍然倾向于查询绝对低效。我玩过内连接和左连接——虽然没有显着差异。此外, match 需要bigint(或大于integer,因为它太小)。
我建议:
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
);
Run Code Online (Sandbox Code Playgroud)
更多的规范化以获得更小的表和索引以及更快的访问。为 建立查找表matchversion,matchtype并且region只在 中写入一个小整数 ID match。
看起来像列participant.item0..item5并且highestAchievedSeasonTier可能是integer,但被定义为text?
该列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 ?
Run Code Online (Sandbox Code Playgroud)
由于champion.name没有定义UNIQUE,它可能是错误的给GROUP BY它。这也是低效的。使用participant.championid来代替(加盟champion后,如果您在结果所需要的名字)。
的所有实例LEFT JOIN都是毫无意义的,因为无论如何您在左表上都有谓词和/或使用GROUP BY.
不需要AND-edWHERE条件周围的括号。
在 Postgres 9.4 或更高版本中,您可以改用新的聚合FILTER语法。详细信息和替代方案:
team您已经拥有的部分索引应如下所示以允许仅索引扫描:
CREATE INDEX on team (matchid, id) WHERE winner -- boolean
Run Code Online (Sandbox Code Playgroud)
但从我所见,您可能只需添加一winner列participant并team完全删除该表(除非有更多内容)。
此外,该索引不会有太大帮助,因为(从您的查询计划中得知)该表有 800k 行,其中一半符合条件:
Run Code Online (Sandbox Code Playgroud)rows=399999 ... Filter: (winner = 'True'::text) ... Rows Removed by Filter: 399999
match当您有更多不同的匹配类型和匹配版本时,此索引将有所帮助(稍后):
CREATE INDEX on match (matchtype_id, matchversion_id, match_id);
Run Code Online (Sandbox Code Playgroud)
尽管如此,虽然 400k 行中有 100k 行符合条件,但该索引仅对仅索引扫描有用。否则,顺序扫描会更快。索引通常会支付大约 5% 或更少的表。
您的主要问题是您显然正在运行一个几乎不真实的数据分布的测试用例。使用更具选择性的谓词,索引将更容易使用。
确保您已经配置了基本的 Postgres 设置,例如random_page_cost或work_mem等。
enable_seqscan = on一点不吭就走了。这仅在调试或本地作为最后手段的绝望措施时关闭。
| 归档时间: |
|
| 查看次数: |
2147 次 |
| 最近记录: |