通过 MySQL InnoDB 行锁的应用程序互斥

发布于 2024-11-09 11:39:08 字数 1171 浏览 0 评论 0原文

我的应用程序由几个与通用 MySQL 机器通信的 Apache 服务器组成。该应用程序的一部分允许用户创建未来一小时的约会。我需要一种机制来防止不同的用户同时来自不同的 Apache 实例,预订相同的一小时预约时段。我已经看到在 Oracle 数据库上实现了类似的“系统间互斥”解决方案(基本上是“选择...进行更新”),但还没有处理在 MySQL 上执行相同操作的详细信息。希望有任何建议、代码或文档参考、最佳实践等。我尝试用谷歌搜索,但主要是关于 MySQL 内部互斥体的讨论。

这些是我认为相关的 MySQL 设置(我的代码将具有 try-catch 和所有内容,并且在不解锁其锁定的内容的情况下决不应该放弃,但也必须考虑在这些情况下发生的情况):

mysql> show variables like 'innodb_rollback_on_timeout';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_rollback_on_timeout | OFF   | 
+----------------------------+-------+
1 row in set (0.00 sec)

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 100   | 
+--------------------------+-------+
1 row in set (0.00 sec)

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 | 
+--------------+
1 row in set (0.00 sec)

任何替代解决方案(MySQL 之外)可以推荐吗?我确实也有一个 memcached 实例在运行,但经常被刷新(并且不确定我是否想要 memcachedb 等来实现容错)。

感谢您的帮助...

My application consists of a couple of Apache servers talking to a common MySQL box. Part of the application lets users create one hour appointments in the future. I need a mechanism which prevents different users coming from the different Apache instances at the same time, book the same one hour appointment slot. I've seen a similar "inter-system mutex" solution implemented on Oracle databases (basically 'select ... for update') but haven't dealt with the details on doing the same with MySQL. Would appreciate any advise, code or documentation references, best practices, etc. Did try to google around but mostly discussions about the MySQL internal mutexes come up.

These are my MySQL settings I thought relevant (my code will have try-catch and all and should never bail without unlocking what it locked but have to account for what happens in those cases as well):

mysql> show variables like 'innodb_rollback_on_timeout';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_rollback_on_timeout | OFF   | 
+----------------------------+-------+
1 row in set (0.00 sec)

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 100   | 
+--------------------------+-------+
1 row in set (0.00 sec)

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 | 
+--------------+
1 row in set (0.00 sec)

Any alternate solutions (outside of MySQL) you could recommend? I do have a memcached instance running as well but gets flushed rather often (and not sure I want to have memcachedb, etc. to make that fault tolerant).

Appreciate your help...

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

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

发布评论

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

评论(2

天涯离梦残月幽梦 2024-11-16 11:39:08

还可以使用 MySQL 和 MariaDB 的 GET_LOCK(和 RELEASE_LOCK)函数:

这些函数可用于实现问题中描述的行为。


获取锁my_app_lock_1

SELECT GET_LOCK('my_app_lock_1', 1000); -- lock's name 'my_app_lock_1', timeout 1000 ms
+---------------------------------+
| GET_LOCK('my_app_lock_1', 1000) |
+---------------------------------+
|                               1 |
+---------------------------------+

释放锁:

DO RELEASE_LOCK('my_app_lock_1'); -- DO makes a result set ignored

请注意(引自MariaDB的文档):

  • <块引用>

    名称在服务器范围内被锁定。 如果某个名称已被一个客户端锁定,GET_LOCK() 会阻止另一个客户端对同名锁的任何请求。这允许客户端就给定的锁达成一致name 使用该名称来执行协作咨询锁定。但请注意,它还允许不在合作客户端集合中的客户端无意或有意地锁定名称,从而防止任何合作客户端锁定该名称。减少这种可能性的一种方法是使用特定于数据库或特定于应用程序的锁名称。例如,使用 db_name.strapp_name.str 形式的锁名称。

  • <块引用>

    使用 GET_LOCK() 获取的锁不会与事务交互。

One can also use MySQL's and MariaDB's GET_LOCK (and RELEASE_LOCK) functions:

The functions can be used to realize the behavior described in the question.


Acquiring a lock my_app_lock_1.

SELECT GET_LOCK('my_app_lock_1', 1000); -- lock's name 'my_app_lock_1', timeout 1000 ms
+---------------------------------+
| GET_LOCK('my_app_lock_1', 1000) |
+---------------------------------+
|                               1 |
+---------------------------------+

Releasing the lock:

DO RELEASE_LOCK('my_app_lock_1'); -- DO makes a result set ignored

Please note (the quotes from MariaDB's documentation):

  • Names are locked on a server-wide basis. If a name has been locked by one client, GET_LOCK() blocks any request by another client for a lock with the same name. This allows clients that agree on a given lock name to use the name to perform cooperative advisory locking. But be aware that it also allows a client that is not among the set of cooperating clients to lock a name, either inadvertently or deliberately, and thus prevent any of the cooperating clients from locking that name. One way to reduce the likelihood of this is to use lock names that are database-specific or application-specific. For example, use lock names of the form db_name.str or app_name.str.

  • Locks obtained with GET_LOCK() do not interact with transactions.

近箐 2024-11-16 11:39:08

在这里回答我自己的问题。我们最终(在 PHP 中)所做的事情是这样的:

<?php
$conn = mysql_connect('localhost', 'name', 'pass');
if (!$conn) {
  echo "Unable to connect to DB: " . mysql_error();
  exit;
 }
if (!mysql_select_db("my_db")) {
  echo "Unable to select mydbname: " . mysql_error();
  exit;
 }
mysql_query('SET AUTOCOMMIT=0'); //very important! this makes FOR UPDATE work                                                                                           
mysql_query('START TRANSACTION');
$sql = "SELECT * from my_mutex_table where entity_id = 'my_mutex_key' FOR UPDATE";
$result = mysql_query($sql);
if (!$result) {
  echo "Could not successfully run query ($sql) from DB: " . mysql_error();
  exit;
 }
if (mysql_num_rows($result) == 0) {
  echo "No rows found, nothing to print so am exiting";
  exit;
 }

echo 'Locked. Hit Enter to unlock...';
$response = trim(fgets(STDIN));
mysql_free_result($result);
echo "Unlocked\n";
?>

为了验证它是否可以从两个不同的控制台运行。时间性能比基于标准文件锁的互斥锁要差一些,但仍然可以接受。

Answering my own question here. A variation of this is what we eventually end up doing (in PHP):

<?php
$conn = mysql_connect('localhost', 'name', 'pass');
if (!$conn) {
  echo "Unable to connect to DB: " . mysql_error();
  exit;
 }
if (!mysql_select_db("my_db")) {
  echo "Unable to select mydbname: " . mysql_error();
  exit;
 }
mysql_query('SET AUTOCOMMIT=0'); //very important! this makes FOR UPDATE work                                                                                           
mysql_query('START TRANSACTION');
$sql = "SELECT * from my_mutex_table where entity_id = 'my_mutex_key' FOR UPDATE";
$result = mysql_query($sql);
if (!$result) {
  echo "Could not successfully run query ($sql) from DB: " . mysql_error();
  exit;
 }
if (mysql_num_rows($result) == 0) {
  echo "No rows found, nothing to print so am exiting";
  exit;
 }

echo 'Locked. Hit Enter to unlock...';
$response = trim(fgets(STDIN));
mysql_free_result($result);
echo "Unlocked\n";
?>

To verify it works run from two different consoles. Time performance is a bit worse than standard file lock based mutexes but still very acceptable.

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