Когда я впервые услышал о материализованных представлениях, мой разум был просто взорван. Итак, я могу написать SQL-запрос, а результаты можно будет сохранить в виде отдельной таблицы (и сохранить на диске?). Если этот щенок живет в памяти, он ДОЛЖЕН БЫТЬ быстрее, чем необходимость копаться в глубоких темных глубинах базы данных. Верно?

Да, это правда. Захват данных с диска, просто сидя там, готовый для вас, довольно быстро. Но когда правильно использовать материализованное представление?

С моей точки зрения, если у вас есть довольно большой набор данных , к которым вам часто потребуется доступ, но не часто , это может быть хорошим местом для просмотра циновки.

Вот один из наиболее простых примеров использования из реальной жизни, когда я реализовал материализованное представление для повышения производительности.

Наше приложение подает песни повсюду. Песни, которые мы предоставляем нашим пользователям, должны соответствовать определенным критериям: они не заархивированы, у них есть действующий аудиофайл и несколько других, как вы увидите ниже. Вот что мы делали раньше:

# models/song_version.rb
scope :ready, where("song_versions.state='active' AND song_versions.transcoding_meta -> 'state'='finished' AND artists.state='active'").includes(song: :artist)

Повсюду в нашем коде вы увидите SongVersion.ready (часто с другими условиями и т. Д.).

Это не ужасно, но поскольку это называется так много, и это очень важно, это главный кандидат на оптимизацию.

Итак, позвольте мне показать вам, как начать с создания материализованного представления. Это потребует некоторых знаний о написании старого доброго необработанного sql. К счастью, это довольно простой select запрос. Мы создадим это в rails migration.

Право на. Теперь у нас есть материализованное представление. Эта миграция фактически выполнила запрос SQL, и благодаря этой строке CREATE MATERIALIZED VIEW теперь он сохраняется на диске. Как видно из нашего select оператора, это не вернет ничего, кроме таблицы song_version_id, а для нас более 50 тысяч из них.

Круто, а если какие-то данные изменятся? Что, если, скажем, атрибут song_version state изменится и больше не может быть «готовой версией песни»? О, боже, я так рада, что ты спросил. Для этого мы можем создать функцию. В нашем случае это функция PostgreSQL, также известная как «хранимая процедура». Обратите внимание, что это живет в той же миграции рельсов, что и создание вида коврика.

Ключевым моментом здесь является строка REFRESH MATERIALIZED VIEW ready_song_versions;. Всякий раз, когда эта функция вызывается, она повторно запускает SQL-запрос, который хранится в представлении mat, с новыми результатами данных и продолжает жить на диске, как ожидалось.

Итак, теперь у нас есть хранимая процедура, но когда ее вызывать? Всякий раз, когда любое из условий изменяется, как показано в операторе where в запросе представления мата.

Итак, мы создаем trigger. Триггер - это особый тип хранимой процедуры, которая автоматически выполняется при возникновении события на сервере базы данных.

Я создал триггеры для каждого столбца, упомянутого в where, но для краткости покажу только один. Все они могут быть смоделированы после этого:

Таким образом, здесь говорится, что если какая-либо song_version имеет обновление, удаление или усечение (что также по сути является «удалением») в базе данных своего state, то запустите только что созданное обновление представления mat.

Итак, это в вашем приложении теперь вызовет обновление:

song_version = SongVersion.last
song_version.update_attributes(state: :archived)

Вид коврика обновляется (и ОЧЕНЬ быстро!), id этой версии song_version больше не присутствует в таблице.

Хорошо, так как же на самом деле использовать это в своем проекте Rails? Давайте дадим ActiveRecord информацию об этом новом представлении мата (и его триггере для его обновления).

Вот как:

Вы создаете модель, унаследованную от ActiveRecord. Настройка table_name очень важна (я покажу вам через минуту). Установите для него readonly, чтобы в него нельзя было писать из ActiveRecord, и создайте метод класса refresh, чтобы у вас была возможность при необходимости вызывать обновление из Rails.

Здесь мы используем table_name (в models / song_version.rb):

Теперь, когда мы запускаем SongVersion.ready, мы получаем те же результаты, что и раньше, но намного быстрее, поскольку мы в основном обходили тяжелую работу, которую пришлось бы проделать ActiveRecord, чтобы найти данные, которые удовлетворяют условиям, которые мы установили для «готовых» версий песен.

Давайте посмотрим на прирост производительности!

Вот результат работы терминала:

Мы снизились с 7,8 до 1,7 секунды. Это значительный выигрыш!

Краткое резюме:

Мы (1) создаем материализованное представление, (2) создаем функцию для его обновления, (3) создаем триггеры для вызова обновления, (4) настраиваем модель, чтобы ActiveRecord знал, что делать с этим новым представлением мата, и ( 5) объявить связь и создать область видимости в связанной модели (SongVersion).

Несколько замечаний:

  • Структура материализованных представлений не может быть изменена после того, как они уже были созданы. Если, скажем, у вас есть новое условие для добавления в предложение where, вам придется создать новую миграцию, чтобы сначала отбросить представление мата, а затем воссоздать его с помощью обновленного запроса.
  • Попытка понять, как матовые виды влияют на ваши тесты. Вот почему есть этот refresh метод, который мы создали на ReadySongVersion модели. Мы явно вызываем ReadySongVersion.refresh в нашем spec_helper файле в блоке config.after(:each)
  • Если вы отбрасываете представление мата, чтобы воссоздать его в другой миграции, это также приведет к удалению всех его индексов и триггеров. Вам нужно будет снова добавить эти триггеры (и индексы, если они есть) в новый файл миграции.
  • Прежде чем использовать материализованное представление, взвесьте другие варианты. Спросите себя: «Это нужно кэшировать? это можно просто проиндексировать? »
  • Не каждый тип базы данных поддерживает матовые представления. Мы используем Postgres. Сначала проверьте, будет ли он работать с выбранной вами БД.
  • Всегда проверяйте свои SQL-запросы, которые вы хотите использовать в своем любимом инструменте с графическим интерфейсом SQL (я использую PSequel), прежде чем вы решите поместить его в свою миграцию. убедитесь, что он возвращает то, что вы ожидаете.

Шум.

Я с радостью отвечу на любые вопросы по этому поводу! HMU в комментариях.