У меня есть очень конкретная и довольно сложная потребность предотвратить чтение с массово параллельных (с одинаковыми секундами, иногда одинаковыми миллисекундами) запросов с разных серверов (если быть точным, ониAWS lambdas) на столе под названием Hobby_ideas_articles.
Настройка:
Я, конечно, читал много постов о блокировке строк и думаю, что они могут быть частью решения, но я думаю, что 'm не в базовом select...for update
случае.
Моя таблица Hobby_ideas_articles
и имеет такие записи, как:
hobby_idea_article_id= 1,
hobby_id = 6
url= 'http://exo.example.com',
author = 'john@example.com'
hobby_idea_article_id= 2,
hobby_id = 3
url= 'http://exo.example2.com',
author = 'john@example.com'
hobby_idea_article_id= 3,
hobby_id = 7
url= 'http://exo.example3.com',
author = 'eric@example.com'
, и у меня есть другая таблица с именем Past_Customer_sent_messages
, где записи выглядят как:
past_customer_sent_message_id = 5
hobby_id = 7,
customer_id = 4,
recipient = "john@example.com",
sent_at= "2019-09-10 00:00:00"
past_customer_sent_message_id = 6
hobby_id = 999,
customer_id = 4,
recipient = "eric@example.com",
sent_at= "2019-09-18 00:00:00"
past_customer_sent_message_id = 7
hobby_id = 999,
customer_id = 4,
recipient = "nestor@example.com",
sent_at= "2019-07-18 00:00:00"
Сегодня у меня есть работающий оператор SQL, который , основанный на 2 входах (hobby_id
и customer_id
) (различные значения для каждой лямбды), идет, чтобы получить всеHobby_ideas_articles
с этим hobby_id
и исключить / отфильтровать любой результат, когда сообщение было отправленоОбратитесь к автору (любому клиенту в течение x дней и конкретному customer_id
в течение y часов) (чтобы получить более подробную информацию об особенностях этих условий / ограничений: MySQL - Выберите данные с JOIN, но с предложениями WHEREприменение к сложным и отличным периметрам ).
SELECT
hia.hobby_idea_article_id,
hobby_id,
url,
author,
ces.sent_at
FROM
Hobby_ideas_articles hia
LEFT JOIN
Past_Customer_sent_messages ces
ON
hia.author = ces.recipient
WHERE
hia.hobby_id = HOBBY_ID_INPUT_I_HAVE AND
hia.author IS NOT NULL
AND hia.author NOT IN (
SELECT recipient
FROM Past_Customer_sent_messages
WHERE
(
customer_id = CUSTOMER_ID_INPUT_I_HAVE
AND sent_at > DATE_SUB(NOW(), INTERVAL 30 DAY)
) OR
(
sent_at > DATE_SUB(NOW(), INTERVAL 3 HOUR
)
)
)
GROUP BY hia.author
ORDER BY hia.hobby_idea_article_id ASC
LIMIT 20
Это означает, например:
- в 10:05:03, лямбда выполнит оператор для
hobby_idea_article_id= 4
и customer_id= 7
- в 10:05:04, через доли миллисекунды после, другая лямбда выполнит оператор для
hobby_idea_article_id= 12
и customer_id= 8
... и так далее ...
Единственная гарантия "бизнес-логики" заключается в том, что У меня никогда не будет 2 одновременно работающих лямбд с одной и той же входной парой (hobby_id
, customer_id
) .
Таким образом, этот текущий вопрос SO о , как убедиться, что клиент НИКОГДА не отправляет два быстрых электронных письма (одно за другим через несколько секунд после другого) одному и тому же получателю, когда имеешь дело с запросами, поступающими из массивно параллельных лямбд ?
больнойПроблема заключается в следующем:
в 10:05:03, лямбда-оператор выполняет оператор SQL для hobby_id= 4
и customer_id=
3 и получает эти данные:
hobby_idea_article_id = 2, hobby_id = 4 url = 'http://exo.example2.com', author ='john@example.com'
hobby_idea_article_id = 3, hobby_id = 4 url =' http://exo.example3.com', author='eric@example.com'
, что означает, что я отправлю john@example.com
и eric@example.com
электронное письмо через несколько секунд (выполненное другой лямбдой, которая заботится о переписке по электронной почте)с данными, переданными ему)
в 10:05:03, параллельная лямбда, выполняемая в ту же самую секунду / ms выполняет оператор SQL для hobby_idea_article_id= 4
и customer_id= 7
(действительно, у меня может быть 8 клиентов, которым нужны идеи о хобби "ловить рыбу с Id = 4!).Эта лямбда извлекает примерно те же данные , что и первая лямбда (как вы видите в операторе SQL, ввод customer_id
используется только для фильтрации авторов, если они уже получили сообщение от этого конкретного клиента),Скажем для примера, что он отфильтровывает john
, так как john
уже был отправлен клиентом 12 дней назад с customer_id=
7, поэтому полученные данные здесь:
hobby_idea_article_id= 3,
hobby_id = 4
url= 'http://exo.example3.com',
author = 'eric@example.com'
, что означает, что я отправлю eric@example.com электронное письмо через несколько секунд (в исполнении другой лямбды, которой были переданы эти данные)
Вот проблема: eric @example.com собирается получить 2 быстрых электронных письма , но я абсолютно не хочу позволять такую вещь.Защита, которую я установил в текущем операторе SQL (см. Условия 1 и 2, объясненные здесь ), защищает только от этих повторяющихся быстрых электронных писем, когда я могу использовать постоянную информацию об электронных письмах, уже отправленных на Past_Customer_sent_messages, но так как этопроисходит так близко / одновременно, вторая лямбда не увидит, что уже была (или, точнее, «будет» другой лямбдой через несколько секунд) отправленное сообщение на eric@example.com
.Мне нужно убедиться, что вторая лямбда НЕ будет выводить hobby_idea с author = eric, чтобы предотвратить такую двойную рассылку.
У меня есть два варианта решения, но я думаю, что второе лучше, так как есть проблемас первым.
1.Решение 1. Используйте блокировку строки с select ...for update
?
. Таким образом, когда первая лямбда попадает в SQL, это предотвратит чтение READ во всех строках выходных строк SQL-запроса, делая их,если я правильно понимаю, «невидимый» для любого последующего SELECT.Это означает, что, если вторая лямбда прибывает одновременно, результат строки SQL-выражения первой лямбды даже не будет рассматриваться / найден!
После прочтения я подумал о том, чтобы сделать это в транзакции и перемещаем ВСЕ hobby_idea_articles, которые являются результатами первого оператора SQL со статусом «now_locked_for_emailing», и присваиваем значение true
, а затем разблокируем, «фиксируя» транзакцию.
Затем, когда у меня естьфактически отправил электронное письмо из другой лямбды, И только после того, как он фактически сохранил / записал в базу данных таблицы Past_Customer_sent_messages данные об этом отправленном электронном сообщении **, я изменю статус «current_locked_for_emailing» на false
**.
Блокировка строки была бы полезной для меня в этом контексте, чтобы убедиться, что во время изменения / обновления статуса (эти несколько миллисекунд), чтобы убедиться, что никакая другая лямбда не может прочитать данные.
Будет ли работать этот оператор SQL ниже?Обратите внимание на транзакцию и новое предложение WHERE для 'current_locked_for_emailing'
-- (A) start a new transaction
START TRANSACTION;
-- (B) Get the latest order number
SELECT
hia.hobby_idea_article_id,
hobby_id,
url,
author,
ces.sent_at
FROM
Hobby_ideas_articles hia
LEFT JOIN
Past_Customer_sent_messages ces
ON
hia.author = ces.recipient
WHERE
hia.hobby_id = HOBBY_ID_INPUT_I_HAVE AND
hia.author IS NOT NULL
AND hia.author NOT IN (
SELECT recipient
FROM Past_Customer_sent_messages
WHERE
(
customer_id = CUSTOMER_ID_INPUT_I_HAVE
AND sent_at > DATE_SUB(NOW(), INTERVAL 30 DAY)
) OR
(
sent_at > DATE_SUB(NOW(), INTERVAL 3 HOUR
)
)
) AND
# NEW CLAUSE ON currently_locked_for_emailing
# THAT GOES ALONG WITH THE ROW LOCK STRATEGY
hia.currently_locked_for_emailing = false
GROUP BY hia.author
ORDER BY hia.hobby_idea_article_id ASC
LIMIT 20
# ADD THE NEW FOR UPDATE FOR THE ROW LOCK
FOR UPDATE
-- (C). Update the column `currently_locked_for_emailing` to `true`
UPDATE Hobby_ideas_articles
SET currently_locked_for_emailing = true
WHERE
############### how to say do it for all the same rows which are the result of the
previous SQL statement on above (see (B)
-- (D) commit changes
COMMIT;
1.1. Можете ли вы помочь мне исправить приведенный выше код SQL?
1.2. Неправильно обновить currently_locked_for_emailing
до true
после установки блокировки, но как это сделать раньше?
1.3 Также я не знаю, как утверждать ', пожалуйста, измените currently_locked_for_emailing
на true
для всех строк, которые являютсярезультат SQL внутри (А) выше?
1.4 как "разблокировать" транзакцию?на самом деле, только после обновления статуса current_locked_for_emailing, я в порядке, чтобы разблокировать ti для чтения и записи, но как это сделать?На самом деле я не хочу ждать окончания соединения с сервером.Можете ли вы подтвердить, что блокировка будет снята, как только она достигнет транзакции 'COMMIT' на (D)?
1.5 - это правильно, если сказать, что код выше блокирует только ВСЕ строки, которые являются результатом выводаВЫБРАТЬ, но не ВСЕ строки на всей таблице?Если да, означает ли это, что с помощью LIMIT 20 он будет блокировать только 20 строк результатов, а не все соответствующие строки (я имею в виду, соответствующие предложению WHERE), это будет хорошо, ноЯ хотел бы быть уверен в этом.
1.6 Я читал много сообщений SO ( здесь , что для работы блокировки строк у вас обязательно должен быть индекс ..)Один человек даже говорит: здесь"Мои собственные тесты показывают, что использование для обновления с фильтрами, где фильтры по неиндексированным столбцам приводит к блокировке всей таблицы, в то время как, когда фильтры по индексированным столбцам приводят к желаемому поведению фильтруемогоблокировка строки. "Это правда, на что я должен поместить это тогда, это не похоже на то, где мой простой прост в 1 или в два столбца ... индекс для всех моих столбцов, где предложения были бы сумасшедшим комплексом нет?
2. Решение 2 - дополнить обновление select ... потому что, даже если я правильно понял 1., у меня все еще есть важная проблема:
Если я правильно понимаю, чтоблокировка строки блокирует ВСЕ роПосле того, как мы получили результат SELECT, возникает проблема. Но настоящая блокировка мне нужна не только для строк, которые являются результатом выбора, но мне нужно поставить блокировку строки на ЛЮБУЮ строку, где author имеет то же значение со строкой, которая была внутри результата SELECT.
Позвольте мне объяснить, почему на примере, где я беру те же данные, что и 1.
в 10:05:03, лямбда выполняетОператор SQL для hobby_id = 4 и customer_id = 3 и извлечение этих данных:
hobby_idea_article_id= 2,
hobby_id = 4
url= 'http://exo.example2.com',
author = 'john@example.com'
hobby_idea_article_id= 3,
hobby_id = 4
url= 'http://exo.example3.com',
author = 'eric@example.com'
... что означает, что я через несколько секунд отправлю john@example.com
и eric@example.com
электронное письмо (выполненное другой лямбдой, которой были переданы эти данные)
- срешение блокировки строк из 1. реализовано, теперь мы знаем, что вторая лямбда НЕ сможет выбрать первые две записи выше с hobby_idea_article_id 2 и 3) (круто!), потому что:
- либо попадет вблокировка строки (эти строки ему невидимы), если что-то происходит очень-очень параллельно,
- ИЛИ , потому что он не выберет их, потому что теперь они имеют
'currently_locked_for_emailing'= true
(см. новый оператор SQL WHEREcurrently_locked_for_emailing = 'false'
, - ИЛИ , поскольку электронное письмо было отправлено, и мы уже сохранили тот факт, что оно было отправлено на Past_Customer_sent_messages.
... Но у меня все еще остается БОЛЬШАЯ проблема.
в 10:05:03, вторая лямбда-выражение выполняет оператор SQL для hobby_id = 9 ( это ДРУГОЕ хобби, это основа моей проблемы ) и customer_id = 13 и извлеките эти данные:
hobby_idea_article_id= 4,
hobby_id = 9 //the hobby_id is DIFFERENT from the one above
url= 'http://exo.example3.com',
author = 'eric@example.com'//but the email recipient is still eric@example.com !!!!
Как вы видите, мы имеем особую ситуацию, так как здесь стратегия блокировки строк делаетне работает: действительно Я бы хотел, чтобы эта вторая лямбда НЕ получала эти данные , потому что автор тот же (eric@example.com
), , но он НЕ был заблокирован первымОператору SQL не присвоено currently_locked_for_emailing= true
, поскольку в первом операторе SQL было предложение WHERE для hobby_id=4
... но здесь это другое hobby_id
!!!поэтому строка никогда не была заблокирована, и поэтому строка hobby_idea_article_id= 4
будет захвачена, и я рискну отправить электронное письмо тому же получателю за несколько миллисекунд.
Так что я не уверен, как это сделать, но ** возможноМне нужно что-то вроде комбинированной блокировки строк или, возможно, ** двухрядных блокировок **** (не знаю, как это будет работать), которые бы помещали «блокировку строк» (пока я не обновлюсь с помощью currently_locked_for_emailing = true
) до:
- первые строки, которые являются 'результирующими строками оператора SQL SELECT'
- , но также и ЛЮБОЙ ДРУГОЙ строке
Hobby_ideas_articles
, которая будет иметь аналогичное значение 'author' с ОДНОЙ из результирующих строк SELECT. На 1 и 2 строках я бы применил стратегию транзакции и настройки currently_locked_for_emailing
до true
(до тех пор, пока фактическое электронное письмо не будет отправлено, и я сохраню этот факт на Past_Customer_sent_messages
)
Это правильный подход?Как это сделать в SQL?
Отказ от ответственности : Я пришел из истории Rails, где я использовал ORM (Active Record), делающий все цепочки / объединения / более легкими, более автоматизированными, и ясовсем потерянный здесь с существующими сложными операторами SQL