Nano Hash - криптовалюты, майнинг, программирование

Ecto.Repo.update_all для атомарных обновлений?

В Руководстве по контекстам Phoenix есть раздел, в котором добавлено увеличение количества просмотров страниц. функциональность в фиктивный контекст CMS. Функция, созданная в контексте CMS, выглядит так:

def inc_page_views(%Page{} = page) do
  {1, [%Page{views: views}]} =
    from(p in Page, where: p.id == ^page.id, select: [:views])
    |> Repo.update_all(inc: [views: 1])

  put_in(page.views, views)
end

Перефразируя, inc_page_views берет структуру Page, использует ее id для поиска соответствующей записи в базе данных, использует Repo.update_all для атомарного увеличения счетчика просмотров (см. документацию для примера чередования), гарантирует, что была обновлена ​​только 1 запись, и возвращает новую Page с количество просмотров обновлений.

Почему в этом примере используется Ecto.Repo.update_all/3, а не Ecto.Repo.update/2? Поскольку мы знаем, что хотим работать только с одной записью, кажется странным потенциально обновлять кучу записей и задним числом проверять, что мы этого не сделали, вместо того, чтобы обновлять конкретный Ecto.Changeset, что может выглядеть примерно так:

def inc_page_views(%Page{views: curr_views} = page) do
  page
  |> Page.changeset(%{views: curr_views + 1})
  |> Repo.update()
end

Эта реализация короче/проще, но я предполагаю, что авторы документации Phoenix не использовали ее по уважительной причине. Я подозреваю, что в версии Repo.update должно отсутствовать то свойство атомарного обновления, которое предположительно присутствует в версии Repo.update_all, но я понятия не имею, почему! Может ли кто-нибудь помочь объяснить разницу между этими реализациями и почему документы могли выбрать первое?


Ответы:


1
def inc_page_views(%Page{views: curr_views} = page) do
  page
  |> Page.changeset(%{views: curr_views + 1})
  |> Repo.update()
end

это вводит состояние гонки. Представьте, что вы получаете страницу из базы данных, и у нее views равно 5. Затем, пока вы выполняете функцию выше, другое соединение с базой данных из другого процесса может изменить значение с 5 на 6. Но поскольку эта функция не не зная об этом, он все равно добавит 1 к 5 (сейчас устаревшее значение) и запишет в базу данных значение 6.

В результате вместо правильного значения 7 у вас будет 6.

Способ предотвратить это — использовать блокировки базы данных, делая что-то вроде этого:

Page
|> where(id: ^id)
|> lock("FOR UPDATE")
|> Repo.one!()
|> inc_page_views()

Или просто используйте Repo.update_all, который гарантирует атомарность операции.

31.10.2019
  • Это имеет смысл. Вам нужно будет хотя бы заблокировать эту запись, чтобы избежать состояния гонки, и просто выдача Repo.update() этого не делает. Мне нужно больше читать документы Ecto! 04.11.2019
  • Для будущего меня Repo.update/update! возьмет changeset, а update_all возьмет queryable. Это означает, что update неразрывно связан с шаблоном чтение, изменение (набор изменений), запись, а update_all может выполнять все в одном запросе (выбрать-и-обновить). См. также эту ветку форума elixirforum. 30.01.2021
  • Я снова..., спасибо, будущий я...... 20.02.2021
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..