MM-27575: Use index hints to prevent index_merge_intersection (#15207)

Even after specifying all the 3 columns to coerce MySQL into choosing
the right multi-column index, it sometimes uses 2 separate indices
and does an index_merge of them.

This creates problems because the DeleteAt=0 search is essentially
the entire Posts table, and causes a disastrously bad performance
than even choosing the wrong index (idx_create_at).

The problem with this approach is that we hardcode the decision
to a specific index when MySQL was free to choose the right index
depending on table statistics. However, there does not appear to be
a case where this index can cause regressions than using some other index.

Another option here was to set optimizer_switch="index_merge_intersection=off"
at a session level for a transaction and then switch it back on after the
query is done.

However, this can cause some unintentional consequences because this setting
is only available at a session level and not at a query level.
There is no need to set something at the session level when an index hint suffices.

https://mattermost.atlassian.net/browse/MM-27575
Этот коммит содержится в:
Agniva De Sarker
2020-08-10 22:40:19 +05:30
коммит произвёл GitHub
родитель 3f19a8c011
Коммит 8138600dd1

Просмотреть файл

@@ -708,10 +708,18 @@ func (s *SqlPostStore) getPostsAround(before bool, options model.GetPostsOptions
direction = ">"
sort = "ASC"
}
table := "Posts p"
// We force MySQL to use the right index to prevent it from accidentally
// using the index_merge_intersection optimization.
// See MM-27575.
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
table += " USE INDEX(idx_posts_channel_id_delete_at_create_at)"
}
replyCountSubQuery := s.getQueryBuilder().Select("COUNT(Posts.Id)").From("Posts").Where(sq.Expr("Posts.RootId = (CASE WHEN p.RootId = '' THEN p.Id ELSE p.RootId END) AND Posts.DeleteAt = 0"))
query := s.getQueryBuilder().Select("p.*")
query = query.Column(sq.Alias(replyCountSubQuery, "ReplyCount"))
query = query.From("Posts p").
query = query.From(table).
Where(sq.And{
sq.Expr(`CreateAt `+direction+` (SELECT CreateAt FROM Posts WHERE Id = ?)`, options.PostId),
sq.Eq{"ChannelId": options.ChannelId},
@@ -811,9 +819,17 @@ func (s *SqlPostStore) getPostIdAroundTime(channelId string, time int64, before
sort = "ASC"
}
table := "Posts"
// We force MySQL to use the right index to prevent it from accidentally
// using the index_merge_intersection optimization.
// See MM-27575.
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
table += " USE INDEX(idx_posts_channel_id_delete_at_create_at)"
}
query := s.getQueryBuilder().
Select("Id").
From("Posts").
From(table).
Where(sq.And{
direction,
sq.Eq{"ChannelId": channelId},
@@ -841,9 +857,17 @@ func (s *SqlPostStore) getPostIdAroundTime(channelId string, time int64, before
}
func (s *SqlPostStore) GetPostAfterTime(channelId string, time int64) (*model.Post, *model.AppError) {
table := "Posts"
// We force MySQL to use the right index to prevent it from accidentally
// using the index_merge_intersection optimization.
// See MM-27575.
if s.DriverName() == model.DATABASE_DRIVER_MYSQL {
table += " USE INDEX(idx_posts_channel_id_delete_at_create_at)"
}
query := s.getQueryBuilder().
Select("*").
From("Posts").
From(table).
Where(sq.And{
sq.Gt{"CreateAt": time},
sq.Eq{"ChannelId": channelId},