Индекс по email ест...
 
Уведомления
Очистить всё

Индекс по email есть, а в EXPLAIN всё равно Seq Scan

5 Сообщения
5 Пользователи
3 Реакции
409 Просмотры
lena_bogatyreva
Сообщения: 27
Автор темы
Читает чужие PR бэкенд-разработчица
Присоединился: 3 года назад
[#53]

Полгода на первой работе, и мне дали задачу ускорить вход в админку. Таблица users, около 900 тысяч строк, индекс по email есть, а запрос всё равно идёт 300-400 мс. Смотрю EXPLAIN и вижу Seq Scan. Поиск сделан так:

User.find_by("lower(email) = ?", params[:email].to_s.downcase)

Индекс создавали обычной миграцией add_index :users, :email, unique: true. Почему база его не берёт? Rails 7.1, PostgreSQL 15.


4 ответа
kostya.sokurov
Сообщения: 73
Держит прод бэкенд-разработчик, senior
Присоединился: 4 года назад

Потому что индекс построен по email, а ищете вы по lower(email). Для планировщика это разные выражения, и обычный индекс тут не подходит. Путей два: индекс по выражению или хранить email сразу в нижнем регистре и искать как есть. Индекс по выражению:

class AddLowerEmailIndexToUsers < ActiveRecord::Migration[7.1]
  disable_ddl_transaction!

  def change
    add_index :users, "lower(email)", unique: true,
              name: "index_users_on_lower_email", algorithm: :concurrently
  end
end

Перед этим проверьте, что в базе нет адресов, которые отличаются только регистром, иначе уникальный индекс не соберётся. И присылайте EXPLAIN ANALYZE, а не просто EXPLAIN: там видно реальное время, а не оценку.


Ответ
TimurK62
Сообщения: 31
Читает чужие PR бэкенд-разработчик
Присоединился: 3 года назад

Второй путь в 7.1 делается одной строкой через normalizes:

class User < ApplicationRecord
  normalizes :email, with: ->(email) { email.strip.downcase }
end

User.find_by(email: " Anna@Shop.Test ") # ищет "anna@shop.test"

Нормализация срабатывает и при записи, и в find_by/where с хэшем. Старые записи сама не перепишет, их надо прогнать отдельно, например User.find_each { |u| u.normalize_attribute(:email); u.save! }. Индекс тогда остаётся обычный, по email, и на таблице такого размера вход у меня по замерам стабильно укладывался меньше чем в миллисекунду.


Ответ
1 ответ
Юлия Ф.
Присоединился: 3 года назад

Разобрался с блоками фулстек-разработчица
Сообщения: 11

Только если где-то в коде ищут строкой, where("email = ?", ...), туда нормализация не доберётся. Сама на этом попадалась


Ответ
Валерий40
Сообщения: 33
Читает чужие PR фулстек-разработчик
Присоединился: 4 года назад

Я с MySQL пришёл и сначала вообще не понял, зачем тут lower: там при коллации *_ci сравнение и так идёт без учёта регистра, и обычный индекс работает. В PostgreSQL так не выйдет, либо lower с индексом по выражению, либо тип citext.


Ответ