防止 Web 应用程序中同时发生事务

发布于 2024-09-24 19:39:31 字数 715 浏览 0 评论 0原文

我们有一个 Web 应用程序(它是一个游戏),其中包含许多不同的表单和元素,它们充当按钮并触发服务器上的一些操作。问题是,如果用户单击按钮太快或在两个选项卡中打开网站,然后同时发出一些操作,有时可能会混淆我们的应用程序。我们有一些基本的保护 - MySQL 事务,一些双击防止 Javascript,但无论如何有时有些东西会被跳过。当然,最好的方法是重新设计所有的SQL事务和支持功能,以免使系统混乱。这种混乱的一个例子是同时发出两个更新 - 一个 Web 请求更改了数据库中的某些内容,但第二个请求仍然使用旧数据运行,因此 SQL 更新返回“受影响的行数为零”,因为第一个事务已经更改数据库中的数据。 显而易见的解决方案是在 UPDATE 之前再次读取数据,看看它是否仍然需要更新,但这意味着在各处放置更多的双 SELECT 查询,这不是一个好的解决方案 - 为什么要从数据库读取相同的数据两次? 此外,我们还考虑过使用一些隐藏令牌在服务器上对每个更新请求进行比较,并拒绝具有相同令牌 ID 的操作,但这也意味着涉及代码的很多地方,并且可能会向系统引入新的错误,该系统工作得很好,除了这一问题。

操作流的逻辑如下:如果用户同时发出两个请求,则第二个请求应等待第一个请求完成。但我们还必须考虑到我们的应用程序中存在许多重定向(例如,在 POST 后以避免用户刷新页面时重复 POST),因此该解决方案不应为用户造成死锁。

所以问题是: 什么是最简单的全局修复,可以以某种方式使所有用户操作按顺序进行?有什么好的通用解决方案吗?

我们正在使用 MySQL 和 PHP。

We have a web application (it is a game) with lots of various forms and elements which act as buttons and trigger some actions on server. The problem is that users can sometimes confuse our application if he clicks on buttons too fast or opens the website in two tabs and then issues some actions simultaneously. We have some basic protection - MySQL transactions, some double-click preventing Javascripts, but anyway sometimes something just skips out. Of course, the best way would be to redesign all the SQL transactions and supporting functions in the way that does not allow to confuse the system. One example of such confusion is issuing two updates simultaneously - one web request changes something in the db, but the second request operates still with the old data and so SQL update returns "number of affected rows was zero" because the first transaction has already changed data in the db.
The obvious solution is to read data once again right before UPDATE to see if it still needs updating, but that means putting many more double SELECT queries everywhere, not a good solution - why to read the same data from db twice?
Also we have considered using some hidden token to compare on the server on each updating request and deny operations which have the same token id, but this also means touching really many places of code and possibly introducing new bugs into the system which works just fine except this one problem.

The logic of the action flow would be the following: if the user issued two requests simultaneously, the second request should wait until the first completes. But we must also consider that there are many redirects in our application (for example after POSTs to avoid double POSTing when user refreshes the page), so the solution should not create deadlocks for a user.

So the question is:
what would be the most easy possible global fix which could just somehow make all the user operations sequential? Is there any good universal solution?

We are using MySQL and PHP.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

失眠症患者 2024-10-01 19:39:31

我很高兴您意识到这只是一个糟糕的临时解决方案,您应该优化您的代码。
忘记你的代币和其他东西。
最简单且仍然有效的方法可能是在每个操作的共享文件上拥有独占文件锁。你可以把它想象成一块木头,只有一个人可以持有身份,并且只有持有它的人才可以说话或做某事。

<?php
    $fp = fopen("/tmp/only-one-bed-available.txt", "w+");

    if (flock($fp, LOCK_EX)) { // do an exclusive lock

         // do some very important critical stuff here which must not be interrupted:
         // sleeping.
         sleep(60);
         echo "I now slept 60 seconds";
        flock($fp, LOCK_UN); // release the lock
    } else {
        echo "Couldn't get the lock!";
    }

    fclose($fp);
?>

如果您并行执行此脚本 10 次,则需要 10 分钟才能完成(因为只有一张床可用!)并且您将每 60 秒(大约)看到一次回声“我现在睡了...”。

这会全局序列化该代码的所有执行。
这可能不是您想要的(您希望它基于每个用户,不是吗?)
我确信您有类似用户 ID 的内容,否则请使用外国 IP 地址,并且每个用户都有一个唯一的文件名:

您还可以为一组操作定义锁定文件名。也许用户可以并行地做一些事情,但只有这个操作会出现问题?给它一个标签并将其包含在锁定文件名中!

这个做法确实不错,可以用!只有很少的方法可以更快地做到这一点(mysql内部结构、共享内存段、更高层上的序列化,如负载均衡器......)

通过上面发布的解决方案,您可以在您的应用程序中完成它,这可能很好。
您也可以在 Mysql 中使用相同的方案:
http://dev.mysql.com/doc /refman/5.0/en/miscellaneous-functions.html#function_get-lock

但 PHP 实现可能更容易、更适合您的用例。

如果你真的想踢屁股有一个更优雅的解决方案,
只需查看这个函数 http://www.php.net/manual/ en/function.sem-get.php

I'm glad you realise that its only a bad and temporary solution and you should optimize your code.
Forget your tokens and stuff.
The easiest and still efficient way is probably to have an exclusive file lock on a shared file per operation. You can imagine this like a piece of wood and only one can hold id and only who holds it is allowed to talk or do something.

<?php
    $fp = fopen("/tmp/only-one-bed-available.txt", "w+");

    if (flock($fp, LOCK_EX)) { // do an exclusive lock

         // do some very important critical stuff here which must not be interrupted:
         // sleeping.
         sleep(60);
         echo "I now slept 60 seconds";
        flock($fp, LOCK_UN); // release the lock
    } else {
        echo "Couldn't get the lock!";
    }

    fclose($fp);
?>

If you execute this script 10 times parallel, it will take 10 minutes to finish (because there is only one bed available!) and you will see the echo "I now slept..." every 60 seconds (approximately).

This serializes ALL executions of this code GLOBALLY.
This is probably not what you want (you want it on a per user basis, don't you?)
I am sure you have something like User-IDs, otherwise use the foreign IP-Adress, and have a unique filename for each user:

<?php $fp = fopen("/tmp/lock-".$_SERVER["REMOTE_ADDR"].".txt", "w+"); ?>

You can also define a lock file name for a group of operations. Maby the user can do some stuff parallel but only this operation makes problems? Give it a tag and include it int he lock filename!

This practice really isn't that bad and can be used! There are only little ways to do it faster (mysql internals, shared memory segments, serialisation on a higher layer like load balancer...)

With the solution posted above you can do it in your application which is probably good.
You can use the same scheme in Mysql as well:
http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock

But the PHP implementation is probably easier and better for your use case.

If you really wanna kick ass there an even more elegant solution,
just check out this function http://www.php.net/manual/en/function.sem-get.php

因为看清所以看轻 2024-10-01 19:39:31

两个选项卡和同时操作?您的用户有多快?

无论如何:只允许一个选项卡已经是一个很好的解决方案。

只需一个选项卡,您就可以在 javascript 中堆叠操作,并在获得最后一次调用的返回值后提交一个新操作。

Two tabs and simultaneous action ? How Fast are your users ?

Anyway : allowing only one tab would already be a good solution.

With only one tab, you can stack actions in javascript and submit a new one once you've got the return value form the last call.

山色无中 2024-10-01 19:39:31

使用mysql的get_lock函数怎么样?
另外也许你应该看看mysql的事务

How about using mysql's get_lock function?
Also maybe you should have a look at mysql's transactions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文