尽管没有索引/键,但更改一个 WHERE 字段时,MySQL 查询速度非常慢

Mic*_*l B 4 mysql rdbms query

这对我来说是一个相当令人困惑的问题。我有一个充满棒球统计数据的数据库。运行此查询:

SELECT * FROM hits
JOIN stadiums ON stadiums.gameName = hits.gameName
JOIN players ON (players.gameName = hits.gameName AND players.id = hits.batter)
JOIN games ON games.gameName = hits.gameName
WHERE games.type = 'R'
LIMIT 50
Run Code Online (Sandbox Code Playgroud)

返回:

/* 0 rows affected, 50 rows found. Duration for 1 query: 0.218 sec. */
Run Code Online (Sandbox Code Playgroud)

但是运行这个查询:

SELECT * FROM hits
JOIN stadiums ON stadiums.gameName = hits.gameName
JOIN players ON (players.gameName = hits.gameName AND players.id = hits.batter)
JOIN games ON games.gameName = hits.gameName
WHERE games.leagueLevel = 'mlb'
LIMIT 50
Run Code Online (Sandbox Code Playgroud)

挂了很长时间。游戏表上的索引只有 games.gameName 而没有别的。

SELECT DISTINCT type FROM games给出8个单字符行(VARCHAR 1),包括一个NULL。

SELECT DISTINCT LeagueLevel FROM games 给出 6 个三字符行 (VARCHAR 5),包括一个 NULL。

我不知道为什么第二个查询会异常缓慢,而第一个查询运行得很好。

谢谢你的帮助。

Rol*_*DBA 5

观点#1:您需要查看列值人口

SELECT COUNT(1) rowount,type FROM games GROUP BY type WITH ROLLUP;
SELECT COUNT(1) rowcount,leaguelevel FROM games GROUP BY leaguelevel WITH ROLLUP;
Run Code Online (Sandbox Code Playgroud)

从你的问题中,我总结出两点:

  1. type='R' 的游戏中的行数必须小于游戏表中的行数。
  2. 与游戏表中的行数相比,带有 Leaguelevel='mlb' 的游戏中的行数必须是一个较大的数字(大于表的 5%)。(5% 在查询优化器眼中是一个经验法则)

观点#2:您可能需要重构此查询

请注意,查询将在所有 JOIN 完成后执行 WHERE 部分。如果可以更早地执行 WHERE 部分,则可以帮助减少时间。尝试像这样重新组织查询:

SELECT * FROM hits
JOIN stadiums ON stadiums.gameName = hits.gameName
JOIN players ON (players.gameName = hits.gameName AND players.id = hits.batter)
JOIN (SELECT * FROM games WHERE leagueLevel = 'mlb') games
ON games.gameName = hits.gameName
LIMIT 50;
Run Code Online (Sandbox Code Playgroud)

观点#3:只检索你真正需要的列

我看到你有 SELECT * 并且你有四张桌子(命中、体育场、球员、比赛)。您将有大量重复数据拖入查询中,尤其是在从所有四个表中拖出 gameName 列时。

您应该重新组织查询以仅引入一个 gameName 列:

SELECT hits.gameName,hits.*,players.*,staduims.*,games.* FROM hits
JOIN stadiums ON stadiums.gameName = hits.gameName
JOIN players ON (players.gameName = hits.gameName AND players.id = hits.batter)
JOIN (SELECT * FROM games WHERE leagueLevel = 'mlb') games
ON games.gameName = hits.gameName
LIMIT 50;
Run Code Online (Sandbox Code Playgroud)

此外,如果您不需要命中表中的每一列,则只包含您知道将访问的列。球员、体育场和比赛也是如此。

换句话说,例如,如果您只需要播放器表中的 playerName,那么您不需要在 SELECT 中使用 player.*。您只需要 player.playerName。

观点#4:您可能需要索引 LeagueLevel 列

您需要执行以下操作来制作所需的索引:

ALTER TABLE games ADD INDEX (leagueLevel);
Run Code Online (Sandbox Code Playgroud)

在这样做之前,运行这个

SELECT COUNT(1) rowcount,leaguelevel FROM games GROUP BY leaguelevel WITH ROLLUP;
Run Code Online (Sandbox Code Playgroud)

任何数量大于表的 5% 的 LeagueLevel 值都会导致 MySQL 查询优化器不使用索引。