处理 ON INSERT 触发器时,innodb 表如何锁定?
我有两个 innodb 表:
articles
id | title | sum_votes
------------------------------
1 | art 1 | 5
2 | art 2 | 8
3 | art 3 | 35
votes
id | article_id | vote
------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 2
4 | 2 | 10
5 | 2 | -2
6 | 3 | 10
7 | 3 | 15
8 | 3 | 12
9 | 3 | -2
当一条新记录插入到 votes
表中时,我想更新 sum_votes<
articles
表中的 /code> 字段通过计算所有投票的总和。
问题
如果 SUM() 计算本身非常繁重(votes
表有 700K 条记录),那么
哪种方式更有效。 1.创建触发器
CREATE TRIGGER `views_on_insert`
AFTER INSERT
ON `votes`
FOR EACH ROW
BEGIN
UPDATE `articles` SET
sum_votes = (
SELECT SUM(`vote`)
FROM `votes`
WHERE `id` = NEW.article_id
)
WHERE `id` = NEW.article_id;
END;
2.在我的应用程序中使用两个查询
SELECT SUM(`vote`) FROM `votes` WHERE `article_id` = 1;
UPDATE `articles`
SET sum_votes = <1st_query_result>
WHERE `id` = 1;
第一种方式看起来更干净,但是表会在 SELECT 查询运行的整个过程中被锁定吗?
I have two innodb tables:
articles
id | title | sum_votes
------------------------------
1 | art 1 | 5
2 | art 2 | 8
3 | art 3 | 35
votes
id | article_id | vote
------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 2
4 | 2 | 10
5 | 2 | -2
6 | 3 | 10
7 | 3 | 15
8 | 3 | 12
9 | 3 | -2
When a new record is inserted into the votes
table, I want to update the sum_votes
field in articles
table by calculating the sum of all votes.
The question
Which way is more efficient, if the SUM() calculation itself is a very heavy one (votes
table has 700K records).
1. Creating a trigger
CREATE TRIGGER `views_on_insert`
AFTER INSERT
ON `votes`
FOR EACH ROW
BEGIN
UPDATE `articles` SET
sum_votes = (
SELECT SUM(`vote`)
FROM `votes`
WHERE `id` = NEW.article_id
)
WHERE `id` = NEW.article_id;
END;
2. Using two queries in my application
SELECT SUM(`vote`) FROM `votes` WHERE `article_id` = 1;
UPDATE `articles`
SET sum_votes = <1st_query_result>
WHERE `id` = 1;
1st way seems cleaner, but will the table be locked the whole time the SELECT query runs?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
关于并发问题,您有一个“简单”的方法来防止第二种方法中的任何并发问题,在事务内对文章行执行选择(
For update
现在是隐式的)。对同一篇文章的任何并发插入将无法获得相同的锁,并将等待您。使用新的默认隔离级别,即使不在事务中使用序列化级别,您也不会在事务结束之前看到投票表上有任何并发插入。因此,您的 SUM 应保持连贯或看起来连贯。但是,如果并发事务对同一篇文章插入投票并在您之前提交(并且第二个事务看不到您的插入),则提交的最后一个事务将覆盖计数器,您将失去 1 票。 因此,通过使用 select before 对文章执行行锁定(当然,并在事务中完成工作)。测试很容易,在 MySQL 上打开 2 个交互式会话并使用 BEGIN 启动事务。
如果您使用触发器,则默认情况下您处于事务中。但我认为您还应该对文章表执行选择,以便为运行的并发触发器创建隐式行锁(更难测试)。
在代码中,要小心每一个
投票插入/删除/更新查询
应该执行行锁
之前的相应文章
交易。这并不难
忘记一个。
最后一点:在开始事务之前使用更困难的事务:
这样您就不需要对文章进行行锁,MySQL 将检测到同一行上发生潜在的写入,并将阻止其他事务直到您完成。 但不要使用根据之前的请求计算出的内容。更新查询将等待文章上的锁释放,当第一个事务
COMMIT
释放锁时,应再次进行SUM
计算以进行计数。因此更新查询应包含SUM
或进行加法。在这里你会看到 MySQL 很聪明,如果有 2 个事务尝试执行此操作,同时插入已在并发时间内完成,则会检测到死锁。在序列化级别中,我还没有找到一种方法来获取错误值:
但准备好处理必须重做的中断事务。
About the concurrency problems, you have an 'easy' way to prevent any concurrency problems in the 2nd method, inside your transaction perform a select on the articles line (the
For update
is now implicit). Any concurrent insert on the same article will not be able to obtain this same lock and will wait for you.With the new default isolation levels, without even using serialization level in the transaction you wouldn't see any concurrent insert on the vote table until the end of your transaction. So your SUM should stay coherent or looks like coherent. But if a concurrent transaction insert a vote on same article and commit before you (and this 2nd one does not see your insert), the last transaction to commit will overwrite the counter and you'll loose 1 vote. So perform a row lock on article by using a select before (and do your work in a transaction, of course). It's easy to test, open 2 interactive sessions on MySQL and start transactions with BEGIN.
If you use the trigger you are in a transaction by default. But I think you should perform as well the select on the article table to make an implicit row lock for concurrent triggers running (harder to test).
in code, be careful every
insert/delete/update query on vote
should perform a row lock on the
corresponding article before in the
transaction. It's not very hard to
forget one.
Last point: make harder transactions, before starting the transaction use:
This way you do not need row locks on articles, MySQL will detect that a potential write on the same row occurs and will block the others transaction until you finish. But do not use something you have computed from a previous request. The update query will be waiting for a lock release on articles, when the lock is released by the 1st transaction
COMMIT
the computing ofSUM
should be done again to count. So the update query should contain theSUM
or make an addition.And here you'll see that MySQL is smart, a deadlock is detected if 2 transactions are trying to do this while insert has been done in a concurrent time. In serialization levels I haven't found a way to obtain a wrong value with :
But be ready to handle breaking transaction that you must redo.
试试这个:
PHP:星级评级系统概念?
编辑:更改了架构以允许用户多次为同一图像投票:
try this:
PHP: Star rating system concept?
EDIT: changed schema to allow a user to vote for the same image many times: