когда ты делаешь join
по проиндексированным полям, разумеется
мускуль бежит по строкам (а на диске таблица - это обычный бинарный файл) банальным сравнением строк (кури алгоритмы если надо) - считай, самой быстрой операцией, и вносит в память все подходящие (поэтому нужно select не * а конкретные поля - меньше данных, меньше висит в памяти)
при этом индексы убирают необходимость прохода по заведомо несовпадающим строкам
когда юзаешь where in - то результат IN мускулю тоже приходится держать в памяти, но прочекать при этом придется минимум только те что в where, а при неудачном оптимизаторе - всю таблицу, а при очень неудачном - еще и не один раз
проще говоря, "where in" всегда медленнее join, по сути - это синтаксический сахар для разовых запросов
если и кажется быстрее, то на небольших сетах, где на join больше накладных расходов