PHP + 锁定 MySQL 表失败


PHP + Locking MySQL Table fails

我有一个需要锁定不入的表,但它也需要能够在防止插入的同时进行更新。

function myfunction() {
  $locked = mysql_result(mysql_query("SELECT locked FROM mylock"),0,0);
  if ( $locked ) return false;
  mysql_query("LOCK TABLES mylock WRITE");
  mysql_query("UPDATE mylock SET locked=1");
  mysql_query("UNLOCK TABLES");
  /* I'm checking another table to see if a record doesn't exist already */
  /* If it doesn't exist then I'm inserting that record */
  mysql_query("LOCK TABLES mylock WRITE");
  mysql_query("UPDATE mylock SET locked=0");
  mysql_query("UNLOCK TABLES");  
}

但这还不够,该函数是从另一个脚本再次调用的,并且同时从对该函数的 2 次调用中发生了插入,我不能这样做,因为它会导致重复记录。

这是紧急的,请帮助。我想在字段上使用 UNIQUE,但有 2 个字段(玩家 1、玩家 2),两者都不能包含玩家 ID 的副本。

不良行为: 记录 A = ( 玩家 1: 123 玩家 2: 456 ) 记录 B = ( 玩家 1: 456 玩家 2: 123 )

我刚刚注意到你在代码中遭受了竞争条件的困扰。假设没有错误(请参阅我的评论)...两个进程可以检查并获得"未锁定"结果。"LOCK TABLES"将序列化其访问权限,但它们都会继续认为它们具有锁定,从而重复记录。

你可以把它改写成这样:

mysql_query("LOCK TABLES mylock WRITE");
mysql_query("UPDATE mylock SET locked=1 WHERE locked=0");
$have_lock = mysql_affected_rows() > 0;
mysql_query("UNLOCK TABLES");
if (!$have_lock ) return false;

我建议根本不使用锁。相反,在输入数据时,请执行以下操作:

mysql_query("INSERT IGNORE INTO my_table VALUES(<some values here>)");
if(mysql_affected_rows()>0)
{
    // the data was inserted without error
    $last_id = mysql_insert_id();
    // add what you need here
}
else
{
    // the data could not be inserted (because it already exists in the table)
    // query the table to retrieve the data
    mysql_query("SELECT * FROM my_table WHERE <some_condition>");
    // add what you need here
}

IGNORE关键字添加到插入语句时,MySQL将尝试插入数据。如果由于表中已有具有相同主键的记录而不起作用,它将以静默方式失败。mysql_affected_rows用于了解插入记录的数量并决定要执行的操作。

这里不需要表级锁定,最好使用行级锁定。行级别锁定意味着仅锁定他们正在修改的一行。通常的替代方法是在修改期间锁定整个表,或者锁定表的某个子集。行级锁定只是将行的子集减少到仍能确保完整性的最小数量。

在InnoDB事务模型中,目标是将多版本数据库的最佳属性与传统的两阶段锁定相结合。InnoDB 在行级别执行锁定,默认情况下以 Oracle 的样式将查询作为非锁定一致性读取运行。InnoDB中的锁定表存储空间高效,因此不需要锁定升级:通常,允许多个用户锁定InnoDB表中的每一行或行的任何随机子集,而不会导致InnoDB内存耗尽。

如果您的问题尚未解决,则内存大小可能是问题所在。InnoDB将其锁表存储在主缓冲池中。这意味着您可以同时拥有的锁数量受到启动MySQL时设置的innodb_buffer_pool_size变量的限制。默认情况下,MySQL将其保留为8MB,如果您在服务器上使用InnoDB执行任何操作,则这是毫无用处的。

幸运的是,解决此问题非常简单:将innodb_buffer_pool_size调整为更合理的值。但是,该修复确实需要重新启动MySQL守护程序。根本没有办法即时调整这个变量(在撰写本文时,使用当前稳定的MySQL版本)。

在调整变量之前,请确保您的服务器可以处理额外的内存使用量。innodb_buffer_pool_size变量是服务器范围的变量,而不是每线程变量,因此它在与MySQL服务器的所有连接(如查询缓存)之间共享。如果您将其设置为1GB,MySQL将不会预先使用所有这些。随着MySQL找到更多的东西放在缓冲区中,内存使用量将逐渐增加,直到达到1GB。此时,当需要存在新数据时,最旧和最少使用的数据开始被修剪。