Problem był taki, jak opisałem w aktualizacji 2 mojego pytania. MySQL używa indeksów do szybkiego wykonywania operacji ORDER BY. Dokładniej, MySQL używa B-drzewa do indeksowania kolumn (takich jak znaczniki czasu - p.time/r.time), które zajmują nieco więcej miejsca, ale umożliwiają szybsze sortowanie.
Problem z moim zapytaniem polegał na tym, że sortowało ono według kolumny czasu w dwóch tabelach, używając sygnatury czasowej z tabeli repost, jeśli jest dostępna, i tabeli postów w przeciwnym razie. Ponieważ MySQL nie może łączyć B-drzewa z obu tabel, nie może przeprowadzić szybkiego sortowania indeksów kolumn z dwóch różnych tabel.
Aby rozwiązać ten problem, zmodyfikowałem moje zapytanie i strukturę tabeli na dwa sposoby.
1) Najpierw wykonaj filtrowanie na podstawie zablokowanych użytkowników, więc zamawianie musi odbywać się tylko na postach, które są dostępne dla bieżącego użytkownika. Nie to było źródłem problemu, ale praktyczną optymalizacją. np.
SELECT * FROM (SELECT * FROM Post p WHERE p.author_id NOT IN (4, 5, 6...))...
2) Traktuj każdy post jako repost przez jego autora, więc każdy post ma gwarancję, że można dołączyć do repostu i repost.time, w którym można go indeksować i sortować. np.
SELECT * FROM (...) LEFT JOIN p.reposts repost ON (p.id = repost.post_id AND
repost.time = (
SELECT MIN(r.time) FROM Repost r WHERE p.id = r.post_id
AND r.user_id IN (1, 2, 3...) AND r.user_id NOT IN (4, 5, 6...))
))
WHERE (repost.id IS NOT NULL) ORDER BY repost.time DESC LIMIT 0, 10
Ostatecznie problem sprowadził się do ORDER BY - takie podejście skróciło czas zapytania z około 8 sekund do 20 ms.