#php #mysql #laravel #relational-database
#php #mysql #laravel #реляционная база данных
Вопрос:
Рассмотрим следующую таблицу adverts
(создание с использованием Laravel, но код довольно понятен):
Schema::create('adverts', function (Blueprint $table) {
$table->increments('id'); //PRIMARY KEY
$table->string('name', 150); //VARCHAR
$table->text('description'); //TEXT
$table->integer('city_id')->nullable();
$table->foreign('city_id')->references('id')->on('cities');
$table->timestamps();
});
В adverts
таблице больше столбцов, но это те, которые необходимы прямо сейчас, imo. Я хочу, чтобы пользователи могли находить объявления с помощью функции текстового поиска, поэтому я добавил следующий FULLTEXT
индекс:
ALTER TABLE posts ADD FULLTEXT search(name, description)
Как вы можете видеть, каждое объявление также имеет city_id
внешний ключ, который позволяет пользователю выполнять поиск adverts
в city
, а также в городах в пределах заданного радиуса. Для этой цели у меня есть radius
таблица, которая содержит записи о парах городов и их расстоянии друг от друга:
| city_A | city_B |distance|
----------------------------------
| 1 | 2 | 30 |
| 2 | 1 | 30 |
radius
Таблица содержит около 7 000 000 записей, и все ее столбцы проиндексированы. Я засеял adverts
таблицу примерно 100 000 поддельными записями (описания составляют около 700 символов). При поиске объявлений я выполняю следующий запрос Laravel:
Advert::whereRaw('MATCH(name, description) AGAINST(? IN BOOLEAN MODE)', [$term])
->join('radius', function ($join) use ($radius, $id){
$join->on('radius.secondary_id','=', 'posts.city_id');
})->where('radius.primary_id', $id)
->whereBetween('radius.radius', [0,$radius]);
В SQL это переводится как:
SLECT * FROM `adverts`
INNER JOIN `radius` on `radius`.`city_B` = `posts`.`city_id`
WHERE MATCH(name, description) AGAINST(? IN BOOLEAN MODE)
AND `radius`.`city_A` = ?
AND `radius`.`radius` between ? and ?
ORDER BY `created_at` desc
LIMIT 20
Итак, как я уже сказал, в adverts
таблице есть еще несколько столбцов, но они, похоже, не вызывают серьезных проблем при выполнении поискового запроса. Однако, как только я объединяю поиск по FULLTEXT
терминам с поиском по близости города, выполнение запроса на моем локальном хосте занимает довольно много времени — где-то около 10-15 секунд. Похоже, что выполнение a MATCH
для всей adverts
таблицы работает нормально, radius
поиск работает нормально, но в сочетании таким образом они приводят к довольно медленному отклику.
Могу ли я что-то сделать, чтобы улучшить таблицы или операторы запроса, или это тот случай, когда MySQL просто не справляется?
Мои спецификации для записи:
CPU: Intel Core i5 3450 @ 3.10GHz Quad
RAM: 8,00GB Dual-Channel DDR3 @ 665MHz
HDD: Seagate ST3320613AS ATA Device (SATA) 7200RPM 16MB Cache
РЕДАКТИРОВАТЬ: MySQL объясняет запрос внутреннего соединения:
---- ------------- ------- ------ --------------- ----- --------- ----- ------ -------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---- ------------- ------- ------ --------------- ----- --------- -------- --- --
| 1 | SIMPLE |adverts|fulltxt|adverts_city_id_foreign,| search | 0 | | 1 | Using where; Using filesort |
| | | | | search | | |
------------------------------------------------------------------------------------------
| 1 | SIMPLE | radius | ref | city_A_index,| city_A_index| 5 | test_db.adverts.city_id | 1417 | Using where |
| | | | | city_B_index,| | |
| | | | | distances_ind| | |
---- ------------ -------- ------ -------------- ------------- --- --- --- ---
Комментарии:
1. Используйте
explain SELECT * FROM adverts ...
и публикуйте результат. Возможно, вам потребуется настроить несколько индексов, но вам нужно проверить, как MySQL выполняет объединение. Еще одна хорошая вещь — опубликовать версию MySQL и убедиться, что вы используете самую последнюю версию (я добавил проблемы с использованием более старых версий).2. Для ваших тестов вы удалили
order by
? Его удаление даст совершенно другой результат. Я бы попробовал индексы индексовcreated_at, city_id
иcity_A, radius
.3. Я сделаю больше тестов завтра, но как вы думаете, это могло бы помочь сделать
where city_id in(...)
и просто выполнить подвыборку в таблице radius, а не объединение?4. @milz Я обновил вопрос результатом
EXPLAIN
, используемым для запроса внутреннего соединения. Я совершенно не понимаю, что означает большая часть этого. Индекс поиска в таблице adverts являетсяFULLTEXT search(name,description)
индексом. Я подумал — может ли создать индекс в таблице radius следующим образом:radius(city_B, distance)
help? Я предполагаю, что это было бы неплохо, учитывая, что пользователи всегда будут искатьadverts
в видеWHERE radius.city_B = ? AND radius.distance between 0 AND ?
5. Это может помочь, но я не думаю, что это проблема. Пробовали ли вы выполнить запрос без
ORDER BY
created_at`desc? If you check the column
Extra` в объяснении, вы увидите, что для таблицы adverts MySQL использует where и filesort . Вот в чем, я думаю, проблема. Я бы пошел с новым индексом вadverts.created_at
таблице. Дайте мне знать, как это происходит. Спасибо!