【问题标题】:How To Prevent Duplicate Rows In MySQL with PHP?如何使用 PHP 防止 MySQL 中的重复行?
【发布时间】:2021-03-27 22:34:35
【问题描述】:

我编写了一个简单的系统来收集有关我网站中用户的数据。

对于每个新用户,我的客户端代码 (Javascript) 会创建一个唯一 ID,将其存储在 cookie 中,然后使用此唯一 ID 标识符将所有数据发送到服务器。

在服务器上,我有以下 PHP 代码检查此用户是否存在于 USERS 表中,如果存在,它将使用该用户 ID 进行其他数据插入,如果不存在,它将创建一个新用户:

$stmt = $conn->prepare("SELECT User_ID FROM USERS WHERE Unique_ID=:uid");
$stmt->bindValue(':uid', $_GET['unique_id'], PDO::PARAM_STR);
$stmt->execute();
$row = $stmt->fetch();               

if ($row) {
    ///// This user exists in USERS Table
    $User_ID = $row['User_ID'];
}
else
{
   /////INSERT a new user to USERS table with this Unique_ID
}

在实时站点上运行此程序后,有时我会在 USERS 表中获得多个具有相同 Unique_ID 的条目。

我进一步检查,这些重复的用户行是用不到 1 秒的设备写入的。所以我认为当客户端的浏览器一次发送 2 个数据请求时会发生这种情况(这对我的应用程序逻辑来说是正常且典型的)。

我尝试将 USERS 表引擎从 INNODB 更改为 MISAM - 强制执行表锁定而不是行锁定 - 它没有帮助。

为了防止重复值,我知道我可以向 Unique_ID 字段添加唯一索引或执行“INSERT IGNORE ON DUPLICATE” - 但是当我需要时,这些选项不会返回正确的用户行。

知道我应该在这里做什么吗?

谢谢!

【问题讨论】:

  • 但是当我需要时,这些选项不会返回正确的用户行。 INSERT 永远不会返回行。这与插入的原始值或重复值无关。
  • 这并不奇怪,你的 php 脚本在同一时间有 2 个调用,并且都失败了初始 SELECT 并运行 else 块。它可能不会经常发生,但它会在某个时候发生
  • 您可以在 3 个地方处理它:客户端(间隔您的请求)、php 端(信号量/监视器或调度)和数据库端(唯一索引或事务/存储过程)。 IMO(并且根据 cmets)最好和最简单的是唯一索引,但是您需要稍微更改 php 脚本
  • 这个想法是select,如果没有找到用户:insert ignore,如果插入有效:获取lastInsertID,否则再次select(1 查询如果一切顺利,3 用于最差的情况)。或者在脚本开头总是insert ignore,然后是select(总是2个查询,即使用户已经存在)
  • 如果您更改您的 php 代码以遵循我上次评论的两个“算法”之一,您永远不会遇到 $User_ID 未定义的情况。 (编辑:抱歉,我不能聊天,我得走了:)

标签: php mysql sql-insert innodb myisam


【解决方案1】:

鉴于您所说的需要维护应用程序的工作方式,您应该使用队列。您可以创建自己的请求,将这些请求记录到另一个表中,然后按顺序由该表中的(新版本)该函数处理 - 一次一个。或者使用像 AWS SQS 这样的 SAS 队列。这将解决您的问题并维护您拥有的流程。

【讨论】:

  • 这是个好主意,我可以这样做,但在我走这条路之前,我想更多地了解这个问题,因为我对这些重复的插入感到非常惊讶。你知道为什么这里的表锁不够吗?此外,为了进行测试,我在浏览器上创建了一个 JS 循环,该循环一次将 5 个此类请求发送到具有相同唯一 ID 的服务器,但我无法重现该问题 - 但它一直在我的访问者身上发生。
  • 我认为问题在于首先检查重复项,并且当表被锁定为只是读取时会生成响应。因此,您的代码会检查它们是否不存在的重复项,然后等待解锁然后写入。
【解决方案2】:

使用UUID()(或客户端中的等效项)代替当前时间。

【讨论】:

    【解决方案3】:

    正如您提到的,您已经添加了对用户 ID 和表级锁的检查,您很好,

    您的客户发送多个请求来存储数据,这是您遇到的主要问题。 最常见的原因是,您必须在发送请求的表单上有一些提交或保存按钮被意外或故意点击两次。

    在发送请求之前,您需要为该按钮使用禁用属性,因为用户单击该按钮应该会停止您的多次点击请求。

    之后,如果您仍然有问题并且必须使用 JS 生成 id,您可以通过将每个请求记录到具有未处理状态的某个日志表来解决问题,并让一些 cronjob 一个一个地更新/处理这些记录到您的用户表。

    【讨论】:

    • 谢谢,但数据不是通过表单发送的——它是一个在后台运行的 JS 脚本,正在收集和发送这些数据。
    • 是什么让你觉得涉及到任何形式?
    猜你喜欢
    • 2012-03-24
    • 2015-04-13
    • 1970-01-01
    • 2022-01-25
    • 2017-04-05
    • 2019-12-04
    • 1970-01-01
    • 1970-01-01
    • 2020-07-27
    相关资源
    最近更新 更多