diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index 0312058075..192b8d2834 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -2423,16 +2423,35 @@ func (s *SqlPostStore) PermanentDeleteBatchForRetentionPolicies(now, globalPolic // DeleteOrphanedRows removes entries from Posts when a corresponding channel no longer exists. func (s *SqlPostStore) DeleteOrphanedRows(limit int) (deleted int64, err error) { + var query string // We need the extra level of nesting to deal with MySQL's locking - const query = ` - DELETE FROM Posts WHERE Id IN ( - SELECT * FROM ( - SELECT Posts.Id FROM Posts - LEFT JOIN Channels ON Posts.ChannelId = Channels.Id - WHERE Channels.Id IS NULL - LIMIT ? - ) AS A - )` + if s.DriverName() == model.DatabaseDriverMysql { + // MySQL fails to do a proper antijoin if the selecting column + // and the joining column are different. In that case, doing a subquery + // leads to a faster plan because MySQL materializes the sub-query + // and does a covering index scan on Posts table. More details on the PR with + // this commit. + query = ` + DELETE FROM Posts WHERE Id IN ( + SELECT * FROM ( + SELECT Posts.Id FROM Posts + WHERE Posts.ChannelId NOT IN (SELECT Id FROM Channels USE INDEX (PRIMARY)) + LIMIT ? + ) AS A + )` + } else { + query = ` + DELETE FROM Posts WHERE Id IN ( + SELECT * FROM ( + SELECT Posts.Id FROM Posts + LEFT JOIN Channels ON Posts.ChannelId = Channels.Id + WHERE Channels.Id IS NULL + LIMIT ? + ) AS A + )` + + } + result, err := s.GetMasterX().Exec(query, limit) if err != nil { return diff --git a/store/sqlstore/thread_store.go b/store/sqlstore/thread_store.go index 735e4b835f..de402c701f 100644 --- a/store/sqlstore/thread_store.go +++ b/store/sqlstore/thread_store.go @@ -900,16 +900,33 @@ func (s *SqlThreadStore) PermanentDeleteBatchThreadMembershipsForRetentionPolici // DeleteOrphanedRows removes orphaned rows from Threads and ThreadMemberships func (s *SqlThreadStore) DeleteOrphanedRows(limit int) (deleted int64, err error) { + var threadsQuery string // We need the extra level of nesting to deal with MySQL's locking - const threadsQuery = ` + if s.DriverName() == model.DatabaseDriverMysql { + // MySQL fails to do a proper antijoin if the selecting column + // and the joining column are different. In that case, doing a subquery + // leads to a faster plan because MySQL materializes the sub-query + // and does a covering index scan on Threads table. More details on the PR with + // this commit. + threadsQuery = ` DELETE FROM Threads WHERE PostId IN ( SELECT * FROM ( + SELECT Threads.PostId FROM Threads + WHERE Threads.ChannelId NOT IN (SELECT Id FROM Channels USE INDEX(PRIMARY)) + LIMIT ? + ) AS A + )` + } else { + threadsQuery = ` + DELETE FROM Threads WHERE PostId IN ( + SELECT * FROM ( SELECT Threads.PostId FROM Threads LEFT JOIN Channels ON Threads.ChannelId = Channels.Id WHERE Channels.Id IS NULL LIMIT ? ) AS A )` + } // We only delete a thread membership if the entire thread no longer exists, // not if the root post has been deleted const threadMembershipsQuery = `