我的 SQL 5.6 - 如何防止读取先前 SQL 语句选择的行加上在给定列中共享重复值的任何其他行

My SQL 5.6 - how to prevent Reads on rows selected by a previous SQL statement PLUS any other row which share a duplicate value in a given column

我有一个非常具体且相当复杂的需求来防止来自不同服务器的大规模并发(同一秒,有时是同一毫秒)请求的读取(到准确地说,它们是 AWS lambdas)在一个名为 Hobby_ideas_articles.

的 table 上

设置:

我当然阅读了很多关于行锁的帖子,并认为它们可能是解决方案的一部分,但我认为我不属于基本的 select...for update 案例。

我的 table 是 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 的 table,其中的记录如下:

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_idcustomer_id(每个输入的不同值lambdas), 将使用给定的 hobby_id 和 exclude/filter 获取所有 Hobby_ideas_articles 在最近向作者发送消息时的任何结果(由任何客户在 x 天内和特定customer_id 在 y 小时内)(详细了解这些 conditions/restrictions 的细节:)。

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

这意味着例如:

唯一的 "business logic" 保证是 我永远不会有 2 个具有相同输入对的并发 lambdas (hobby_id, customer_id) .

所以当前的 SO 问题是关于 如何确保客户在处理即将到来的请求时永远不会向同一个收件人发送两封快速电子邮件(一封紧接另一封仅几秒钟)来自大规模并发 lambdas ?

问题的一个例子是:

这意味着我将在几秒钟后发送 john@example.comeric@example.com 一封电子邮件(由另一个 lambda 执行,负责处理传递给它的数据的电子邮件)

这意味着我将在几秒钟后向 eric@example.com 发送一封电子邮件(由传递此数据的另一个 lambda 执行)

问题是:eric@example.com 将收到 2 封快速电子邮件,但我绝对不想允许这样的事情发生。我在当前 SQL 声明中实施的保护措施(请参阅条件 1 和 2 解释 ),仅当我可以使用有关已在 [= 上发送的电子邮件的持久信息时,才能防止这些重复的快速电子邮件299=] 但由于这种情况发生得如此接近/如此同时发生,第二个 lambda 将看不到已经(或者更准确地说 "is going to be " 几秒钟后另一个 lambda 发送了一条消息到 eric@example.com。 我需要确保第二个 lambda 不会输出带有 author=eric 的 hobby_idea 以防止此类双重电子邮件。

我有两个想法解决方案,但我认为第二个更好,因为第一个有问题。

1.解决方案 1 - 使用带有 select ...for update ?

的行锁

这样,当第一个 lambda 命中 SQL 时,它将阻止对 SQL 查询输出行的所有行进行读取,如果我理解正确的话,使它们成为 "invisible" 到任何后续的 SELECT。这意味着如果第二个 lambda 同时到达,第一个 lambda 的 SQL 语句行的结果将不是偶数 considered/found!

读完之后,我考虑在 transaction 中进行操作,并移动所有 hobby_idea_articles,这是第一个 SQL 语句的结果状态"currently_locked_for_emailing",赋值true,然后通过"commiting"交易解锁

然后,当我实际上已经从另一个 lambda 发送了电子邮件,并且只有在 persisted/written 在 Past_Customer_sent_messages table 上的数据库上实际发送了关于这封电子邮件的数据之后* *, 我会把 'currently_locked_for_emailing' 的状态改回 false**.

在这种情况下,行锁对我很有用,可以确保在我更改/更新状态(几毫秒)时,确保没有其他 lambda 可以读取数据。

下面的 SQL 语句行得通吗?注意 'currently_locked_for_emailing'

上的事务和新的 WHERE 子句
-- (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_emailingtrue感觉不对但是之前怎么办?

1.3 我也不知道如何断言'请将所有行的 currently_locked_for_emailing 更改为 true ,这是 SQL 的结果在 (A) 里面 ?

1.4如何进行"unlock"交易?确实在更新 currently_locked_for_emailing 状态后,我可以解锁 ti 进行读写,但如何做到这一点?事实上,我不想等待与服务器的连接结束。请确认锁定将在 (D) 到达交易 'COMMIT' 后立即移除?

1.5 上面的代码只锁定所有作为 SELECT 结果输出的行而不是整个 table 的所有行是否正确? 如果是,是否意味着通过使用LIMIT 20,它只会锁定结果的20行,而不是所有匹配的行(我的意思是对应于WHERE子句),没关系,但我想确定这一点。

1.6 我读了很多 OT SO 帖子 (here, that for a row lock to work, you must absolutely have an index... One person even says here "My own tests show that using for update with where filters on non-indexed columns results in whole-table locking, while with where filters on indexed columns results in the desired behaviour of filtered row locking. " 是真的吗,那我应该把它放在什么上面,它不像我的 where 是一个简单的 1 列或两列...我所有 where 子句列上的索引会非常复杂不是吗?

2。解决方案 2 - 补充 select...更新,因为即使我得到 1. 正确,我仍然有一个重要问题:

如果我正确理解 'row lock' 锁定了 SELECT 结果内的所有行,那么问题就出在这里。 但我需要的真正锁不仅是 select 结果的行,而且我需要对作者具有相同值的任何行放置行锁在 SELECT

的结果中

让我用一个例子来解释为什么,我使用与 1 相同的数据。

...这意味着我将在几秒钟后向 john@example.comeric@example.com 发送一封电子邮件(由传递此数据的另一个 lambda 执行)

...但是我还有个大问题

如您所见,我们有一个特殊的情况,因为这里行锁策略不起作用:确实我希望第二个 lambda 不要获取此数据 因为作者是同一个 (eric@example.com),但它没有被第一个SQL语句锁定,也没有赋值currently_locked_for_emailing= true 因为第一个 SQL 语句有一个 hobby_id=4 的 WHERE 子句......但这里是一个不同的 hobby_id!!!所以该行从未被锁定,因此行 hobby_idea_article_id= 4 将被抓取,我冒着在几毫秒内向同一收件人发送电子邮件的风险。

所以我不确定该怎么做,但**也许我需要像组合行锁或**双行锁****(不确定这将如何工作)这样的东西'row lock'(直到我用 currently_locked_for_emailing = true 更新)到:

这是正确的做法吗?如何在 SQL 中做到这一点?

免责声明:我来自 Rails 背景,我曾经有 ORM(活动记录)使所有 chains/joins/ 更容易更自动地工作现在的 SQL 复杂语句

我完全迷失了

我必须承认,我还没有完全阅读您的问题,因为它很大,但我对您的问题有所了解。不是将发送部分和SQL部分分开的解决方案吗?因此,创建一个名为队列的新 table,并将所有操作插入到一个新的 table 中。然后,您 运行 一个单独的 cron/task 发送电子邮件,只要在过去 X 分钟内未联系特定用户。这样您就可以保持独特感。