MySQL 查询在迁移到 RDS 后卡在 "sending data" 30 秒
MySQL queries stuck in "sending data" for 30 seconds after migrating to RDS
当 MySQL 在与网站其余部分相同的 EC2 实例上进行本地查询时,此查询(以及我认为有相关问题的其他一些查询)不会花费 30 秒。更像是毫秒。
有什么地方不对劲吗?
SELECT *, chv_images.image_id FROM chv_images
LEFT JOIN chv_storages ON chv_images.image_storage_id =
chv_storages.storage_id
LEFT JOIN chv_users ON chv_images.image_user_id = chv_users.user_id
LEFT JOIN chv_albums ON chv_images.image_album_id = chv_albums.album_id
LEFT JOIN chv_categories ON chv_images.image_category_id =
chv_categories.category_id
LEFT JOIN chv_meta ON chv_images.image_id = chv_meta.image_id
LEFT JOIN chv_likes ON chv_likes.like_content_type = "image" AND
chv_likes.like_content_id = chv_images.image_id AND chv_likes.like_user_id = 1
LEFT JOIN chv_follows ON chv_follows.follow_followed_user_id =
chv_images.image_user_id
LEFT JOIN chv_follows_projects ON
chv_follows_projects.follows_project_project_id =
chv_images.image_project_id LEFT JOIN chv_projects ON
chv_projects.project_id = follows_project_project_id WHERE
chv_follows.follow_user_id='1' OR (follows_project_user_id = 1 AND
chv_projects.project_privacy = "public" AND
chv_projects.project_is_public_upload = 1) GROUP BY chv_images.image_id
ORDER BY chv_images.image_id DESC
LIMIT 0,15
And this is what EXPLAIN shows:
谢谢
更新:这个查询有同样的问题。它没有 GROUP BY。
SELECT *, chv_images.image_id FROM chv_images
LEFT JOIN chv_storages ON chv_images.image_storage_id =
chv_storages.storage_id
LEFT JOIN chv_users ON chv_images.image_user_id = chv_users.user_id
LEFT JOIN chv_albums ON chv_images.image_album_id = chv_albums.album_id
LEFT JOIN chv_categories ON chv_images.image_category_id =
chv_categories.category_id
LEFT JOIN chv_meta ON chv_images.image_id = chv_meta.image_id
LEFT JOIN chv_likes ON chv_likes.like_content_type = "image" AND
chv_likes.like_content_id = chv_images.image_id AND chv_likes.like_user_id = 1
ORDER BY chv_images.image_id DESC
LIMIT 0,15
chv_images中有索引吗?
我提议:
CREATE INDEX idx_image_id ON chv_images (image_id);
EXPLAIN 显示了几次 table 扫描 (type: ALL
),因此花费超过 30 秒也就不足为奇了。
这是您的解释:
请注意,rows
列显示了从第一个 table chv_images 读取的估计 14420 行。它正在对所有行进行 table 扫描。
一般来说,当你做一系列的JOIN时,你可以将EXPLAIN的rows
列中的所有值相乘,最后的结果就是读了多少行MySQL必须做。在这种情况下,它是 14420 * 2 * 1 * 1 * 2 * 1 * 916,或 52,834,880 行读取。这应该正确看待在同一个查询中进行多次 table-扫描的高成本。
您可以通过在这些 table 上创建一些索引来帮助避免那些 table 扫描:
ALTER TABLE chv_storages
ADD INDEX (storage_id);
ALTER TABLE chv_categories
ADD INDEX (category_id);
ALTER TABLE chv_likes
ADD INDEX (like_content_id, like_content_type, like_user_id);
尝试创建这些索引,然后再次 运行 EXPLAIN。
其他 table 已经通过主键 (type: eq_ref
) 或辅助键 (type: ref
) 进行查找,因此这些已经过优化。
您的 EXPLAIN 显示您的查询使用临时 table 和文件排序。您应该重新考虑是否需要 GROUP BY,因为这可能会导致额外的工作。
另一个提示是避免使用 SELECT *
,因为它可能会强制查询读取许多您不需要的额外列。相反,只明确命名您需要的列。
(比尔的想法很好,我换个方式讨论...)
Explode-Iplode -- 如果 LEFT JOINs
匹配不超过 1 行,更改,例如,
SELECT
...
LEFT JOIN chv_meta ON chv_images.image_id = chv_meta.image_id
进入
SELECT ...,
( SELECT foo FROM chv_meta WHERE image_id = chv_images.image_id ) AS foo, ...
如果 所有 和 JOINs
都可以做到这一点,您就可以摆脱 GROUP BY
。这将避免昂贵的 "explode-implode",其中 JOINs
导致更多行,然后 GROUP BY
摆脱重复。 (我怀疑你不能移动所有的连接。)
OR -> UNION -- OR
很难优化。您的查询看起来很适合变成 UNION
,然后创建更多有用的索引。
WHERE chv_follows.follow_user_id='1'
OR (follows_project_user_id = 1
AND chv_projects.project_privacy = "public"
AND chv_projects.project_is_public_upload = 1
)
假设 follows_project_user_id
在`chv_images,
( SELECT ...
WHERE chv_follows.follow_user_id='1' )
UNION DISTINCT -- or ALL, if you are sure there won't be dups
( SELECT ...
WHERE follows_project_user_id = 1
AND chv_projects.project_privacy = "public"
AND chv_projects.project_is_public_upload = 1 )
需要索引:
chv_follows: (follow_user_id)
chv_projects: (project_privacy, project_is_public_upload) -- either order
但这还没有处理ORDER BY
和LIMIT
。此类的一般模式:
( SELECT ... ORDER BY ... LIMIT 15 )
UNION
( SELECT ... ORDER BY ... LIMIT 15 )
ORDER BY ... LIMIT 15
是的,ORDER BY
和 LIMIT
重复了。
这适用于第 1 页。如果您想要接下来的 15 行,请参阅 http://mysql.rjweb.org/doc.php/pagination#pagination_and_union
构建完那两个子选择后,看看它们;我认为您将能够优化每一个,并且可能需要新的索引,因为优化器将从不同的 'first' table.
开始
当 MySQL 在与网站其余部分相同的 EC2 实例上进行本地查询时,此查询(以及我认为有相关问题的其他一些查询)不会花费 30 秒。更像是毫秒。
有什么地方不对劲吗?
SELECT *, chv_images.image_id FROM chv_images
LEFT JOIN chv_storages ON chv_images.image_storage_id =
chv_storages.storage_id
LEFT JOIN chv_users ON chv_images.image_user_id = chv_users.user_id
LEFT JOIN chv_albums ON chv_images.image_album_id = chv_albums.album_id
LEFT JOIN chv_categories ON chv_images.image_category_id =
chv_categories.category_id
LEFT JOIN chv_meta ON chv_images.image_id = chv_meta.image_id
LEFT JOIN chv_likes ON chv_likes.like_content_type = "image" AND
chv_likes.like_content_id = chv_images.image_id AND chv_likes.like_user_id = 1
LEFT JOIN chv_follows ON chv_follows.follow_followed_user_id =
chv_images.image_user_id
LEFT JOIN chv_follows_projects ON
chv_follows_projects.follows_project_project_id =
chv_images.image_project_id LEFT JOIN chv_projects ON
chv_projects.project_id = follows_project_project_id WHERE
chv_follows.follow_user_id='1' OR (follows_project_user_id = 1 AND
chv_projects.project_privacy = "public" AND
chv_projects.project_is_public_upload = 1) GROUP BY chv_images.image_id
ORDER BY chv_images.image_id DESC
LIMIT 0,15
And this is what EXPLAIN shows:
谢谢
更新:这个查询有同样的问题。它没有 GROUP BY。
SELECT *, chv_images.image_id FROM chv_images
LEFT JOIN chv_storages ON chv_images.image_storage_id =
chv_storages.storage_id
LEFT JOIN chv_users ON chv_images.image_user_id = chv_users.user_id
LEFT JOIN chv_albums ON chv_images.image_album_id = chv_albums.album_id
LEFT JOIN chv_categories ON chv_images.image_category_id =
chv_categories.category_id
LEFT JOIN chv_meta ON chv_images.image_id = chv_meta.image_id
LEFT JOIN chv_likes ON chv_likes.like_content_type = "image" AND
chv_likes.like_content_id = chv_images.image_id AND chv_likes.like_user_id = 1
ORDER BY chv_images.image_id DESC
LIMIT 0,15
chv_images中有索引吗?
我提议:
CREATE INDEX idx_image_id ON chv_images (image_id);
EXPLAIN 显示了几次 table 扫描 (type: ALL
),因此花费超过 30 秒也就不足为奇了。
这是您的解释:
请注意,rows
列显示了从第一个 table chv_images 读取的估计 14420 行。它正在对所有行进行 table 扫描。
一般来说,当你做一系列的JOIN时,你可以将EXPLAIN的rows
列中的所有值相乘,最后的结果就是读了多少行MySQL必须做。在这种情况下,它是 14420 * 2 * 1 * 1 * 2 * 1 * 916,或 52,834,880 行读取。这应该正确看待在同一个查询中进行多次 table-扫描的高成本。
您可以通过在这些 table 上创建一些索引来帮助避免那些 table 扫描:
ALTER TABLE chv_storages
ADD INDEX (storage_id);
ALTER TABLE chv_categories
ADD INDEX (category_id);
ALTER TABLE chv_likes
ADD INDEX (like_content_id, like_content_type, like_user_id);
尝试创建这些索引,然后再次 运行 EXPLAIN。
其他 table 已经通过主键 (type: eq_ref
) 或辅助键 (type: ref
) 进行查找,因此这些已经过优化。
您的 EXPLAIN 显示您的查询使用临时 table 和文件排序。您应该重新考虑是否需要 GROUP BY,因为这可能会导致额外的工作。
另一个提示是避免使用 SELECT *
,因为它可能会强制查询读取许多您不需要的额外列。相反,只明确命名您需要的列。
(比尔的想法很好,我换个方式讨论...)
Explode-Iplode -- 如果 LEFT JOINs
匹配不超过 1 行,更改,例如,
SELECT
...
LEFT JOIN chv_meta ON chv_images.image_id = chv_meta.image_id
进入
SELECT ...,
( SELECT foo FROM chv_meta WHERE image_id = chv_images.image_id ) AS foo, ...
如果 所有 和 JOINs
都可以做到这一点,您就可以摆脱 GROUP BY
。这将避免昂贵的 "explode-implode",其中 JOINs
导致更多行,然后 GROUP BY
摆脱重复。 (我怀疑你不能移动所有的连接。)
OR -> UNION -- OR
很难优化。您的查询看起来很适合变成 UNION
,然后创建更多有用的索引。
WHERE chv_follows.follow_user_id='1'
OR (follows_project_user_id = 1
AND chv_projects.project_privacy = "public"
AND chv_projects.project_is_public_upload = 1
)
假设 follows_project_user_id
在`chv_images,
( SELECT ...
WHERE chv_follows.follow_user_id='1' )
UNION DISTINCT -- or ALL, if you are sure there won't be dups
( SELECT ...
WHERE follows_project_user_id = 1
AND chv_projects.project_privacy = "public"
AND chv_projects.project_is_public_upload = 1 )
需要索引:
chv_follows: (follow_user_id)
chv_projects: (project_privacy, project_is_public_upload) -- either order
但这还没有处理ORDER BY
和LIMIT
。此类的一般模式:
( SELECT ... ORDER BY ... LIMIT 15 )
UNION
( SELECT ... ORDER BY ... LIMIT 15 )
ORDER BY ... LIMIT 15
是的,ORDER BY
和 LIMIT
重复了。
这适用于第 1 页。如果您想要接下来的 15 行,请参阅 http://mysql.rjweb.org/doc.php/pagination#pagination_and_union
构建完那两个子选择后,看看它们;我认为您将能够优化每一个,并且可能需要新的索引,因为优化器将从不同的 'first' table.
开始