Совет дня №27
Представьте, что на кровати лежит книга, и нужно вернуть ее в шкаф. Вместо того, чтобы отнести книгу, вы толкаете шкаф к кровати, кладете книгу на полку, а потом толкаете шкаф на место. Подобная сцена уместна в ситкоме, но в целом это абсурд.
Однако именно так большинство работает с базой данных: без конца гоняет данные туда-сюда, хотя можно обойтись командой.
Предположим, нужно обновить статус пользователа по коду. На ORM это выглядит так:
user = models.User.get_by_id(42)
user.status = 'active'
user.save()
Разумеется, никому не интересно, какие запросы будут выполнены. Их никто не смотрит и даже не знает, как включить (см. прошлый совет). А запросы будут такими:
select * from users
where id = 42
update users set status='active'
where id = 42
Внимание: зачем нужен первый запрос? Мы выбрали все поля пользователя. Зачем?
Второй UPDATE делает именно то, что нужно: обновляет статус по номеру. Ради
этого не стоило читать пользователя! Нужно оставить только второй запрос.
Говорят: перед обновлением я должен убедиться, что пользователь существует. Это
легко сделать и после: оператор UPDATE возвращает число обновленных
записей. Если оно равно нулю, пользователя не было. Не говоря уж о том, что
существование записи проверяется SELECT-ом без полей или условием EXISTS –
незачем выгребать все поля.
Вообще, следите, чтобы данные извлекались из базы как можно реже. База прекрасно умеет обновлять и перекладывать данные без участия клиента. Например, кто-то блокирует запись при чтении, чтобы обновить ее:
begin;
select * from users where id = 42 for update;
update users set ... where id = 42;
commit;
Зачем читать и блокировать запись? Просто обнови ее.
Возразят: я читаю данные, чтобы рассчитать новые поля. Что ж, если логика сложная, это делают в приложении. Но многие вещи можно сделать на SQL. Например, начисление баллов, рейтинга, изменения цены и так далее. Для этого пишут одноразовую функцию:
create or replace pg_temp.get_new_rating(user user)
returns integer
immutable strict parallel safe language sql
return ...
и обновляют сразу много пользователей:
update users
set rating = get_new_rating(users)
FROM subquery
where id = subquery.user_id
Поздапрос subquery выбирает пользователей, которых нужно обновить, а функция
get_new_rating возвращает новый рейтинг.
Уже слышу истерику: омг, хранимки, мои глаза! Ну, тут уж на ваше усмотрение. Хотите пользоваться базой нормально? Понадобится сырой скуль. Для этого нужно отложить ORM и почитать книжки. Но оно того стоит.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter