【问题标题】:Duplicate entries in MySQL after performing a check (PHP)执行检查后 MySQL 中的重复条目 (PHP)
【发布时间】:2013-10-04 08:26:01
【问题描述】:

最近,我的 MySQL 数据库中出现了一些奇怪的重复条目。此表中的条目在对 PHP 页面的 PUT 请求期间插入。该表包含 3 个没有外部引用的字段:

  • manga_id(主键;自动递增)- bigint(20);
  • 名称 - varchar(255);
  • manga_cid - varchar(255)。

PHP 代码如下:

class MangaHandler {

private function getMangaFromName($name) {
    $id = $this->generateId($name);
    $mangas = new Query("SELECT * FROM tbl_manga WHERE manga_cid = '" . $this->conn->escapeString($id) . "'", $this->conn);

    if(!$mangas || $mangas->hasError()) {
        logError("getMangaFromName($name): " . $this->conn->getError());
        return null;
    }

    if($mangas->moveNext()) {
        return $mangas->getRow();
    }

    return null;
}

private function addManga($name) {
    $manga_row = null;
    $error = false;

    $cid = $this->generateId($name);

    $sql = sprintf("INSERT INTO tbl_manga(name, manga_cid) VALUES ('%s', '%s')", $this->conn->escapeString($name), $this->conn->escapeString($cid));
    if(!$this->conn->execute($sql))
        $error = true;

    // some more code ...

    if($error) {
        logError("addManga($name): " . $this->conn->getError());
    }

    return $manga_row;
}

public function addMangaSourceAndFollow($name, $url, $source_id, $user_id, $stick_source = false, $stick_lang = 'English') {
        // validate url

        $manga = $this->getMangaFromUrl($url, $source_id);
        if(!$manga) {
            $manga = $this->getMangaFromName($name);
            if(!$manga) $manga = $this->addManga($name);

            // ...

        }

        // ...

        return true;
}
}

class MangaRestService extends CommonRestService 
{
   public function performPut($url, $arguments, $accept, $raw) {
        header('Content-type: application/json');
        header("Cache-Control: no-cache, must-revalidate");

        $json = json_decode($raw, true);

        // some input validation and auth

        $ms = new MangaHandler();

        try {

            $ret = $ms->addMangaSourceAndFollow(null, $json['url'], $source['source_id'], $user['user_id'], $enforce == 1);

            // throw exception if some ret is invalid
            // return proper json response

        } catch(Exception $e) {
            $conn->rollback();
            logError("MangaRestService.performPut($url, [" . implode("; ", $arguments) . "], $accept, $raw): " . $e->getMessage());
            echo RestResponse::getSomeErrorResponse()->toJSON();
        }   
    }
}

$serv = new MangaRestService();
// performs some checks and invokes the appropriate GET, POST, PUT or DELETE method
// in this case it's the performPut method above
$serv->handleRawRequest(); 

漫画名称被过滤(只允许使用字母数字字符、下划线和其他一些字符)并变成ma​​nga_cid(在表格中应该是唯一的)

代码主要检查是否存在具有特定 manga_cid 的漫画。只有在没有的情况下才会插入一个新条目。我已经测试了代码并且它可以工作,但是,在部署应用程序之后。我收到了具有重复 manga_cid 的条目(有时超过 2 个) )。这种情况很少见,因为大多数条目都是唯一的。此外,我的日志中没有错误。

可能是由于某种原因,多个 HTTP PUT 请求同时执行,并且由于没有同步,因此 INSERT 被多次调用? 我觉得这不太可能,特别是因为该应用只允许您按下执行此请求的按钮一次,然后它就会消失。

我尝试过使用 MySQL 事务,但没有解决问题。我知道将字段设置为唯一可能会让我避免这些重复的条目,但是我必须对数据库执行一些繁重的维护才能首先删除重复的条目。尽管此表结构简单,但在其他几个表中都引用了 manga_id。 另外,我很好奇为什么会这样:-)

以防万一,这里是 Query 类:

class Query extends QueryBase
{
    function Query($query, &$conn)
    {
        $this->recordset = array();
        $this->has_error=0;
        $regs = mysqli_query($conn->getConnection(), $query);

        if(!$regs)
        {
            $this->has_error=1;
            return;
        }

        $index = 0;
        $this->current_index=-1;

        while(($row = mysqli_fetch_array($regs, MYSQL_ASSOC)))
        {
            $this->recordset[$index]=$row;
            $index++;
        }

        mysqli_free_result($regs);
    }

    public function moveNext()
    {
        if($this->current_index<(sizeof($this->recordset)-1))
        {
            $this->current_index++;
            return 1;
        }
        else
        return 0;
    }

    public function moveBack()
    {
        if($this->current_index>=1)
        {
            $this->current_index--;
            return 1;
        }
        else
        return 0;
    }

    public function recordCount()
    {
        return sizeof($this->recordset);
    }


    public function get($field)
    {
        return $this->recordset[$this->current_index][$field];
    }

    public function getRow()
    {
        return $this->recordset[$this->current_index];
    }

    public function hasError()
    {
        return $this->has_error;
    }
}

感谢您的帮助。

【问题讨论】:

  • 您可以在manga_cid 列上添加唯一索引。这将防止重复条目,并且您可以在插入时捕获错误。或者使用 mysql update into 语法。
  • 这是您用于查询类的内容吗? code.google.com/p/class-query
  • Query类是我自己写的,直接调用mysqli api。
  • 啊,好吧——问题很可能出在那个班级上。 PDO 可能是一种更加基于标准的方法?

标签: php mysql concurrency duplicates


【解决方案1】:

我不确定这是否会为您解决问题,但最好返回“false;”而不是在您的 getManageFromName() 方法失败时返回“null”。

要检查的另一件事是您的 hasErrors() 方法;有时由于某种原因可能会返回错误?

你最好这样写:

private function getMangaFromName($name) {
    $id = $this->generateId($name);
    $mangas = new Query("SELECT * FROM tbl_manga WHERE manga_cid = '" . $this->conn->escapeString($id) . "'", $this->conn);

    if($mangas->moveNext()) {
        return $mangas->getRow();
    } else {

           if(!$mangas || $mangas->hasError()) {
                 logError("getMangaFromName($name): " . $this->conn->getError());
           }

           return null;
    }
}

// 然后像这样检查:

$manga = $this->getMangaFromName($name);
if ( empty($manga) ) {
    $manga = $this->addManga($name);
}

【讨论】:

  • 给你的问题; $mangas->getRow() 返回什么?我怀疑它总是返回一个数组,如果查询什么都不返回,可能是一个空数组?
  • 是的,它总是返回一个数组。如果查询没有返回任何结果,则 moveNext() 将返回 false,因此永远不会调用 getRow()。我相信只有在没有字段的表之类的东西存在的情况下才有可能使用空数组。
【解决方案2】:

作为一般规则,如果检查值是否存在和插入值的过程是分开的,则可能由于有人不小心多次单击提交按钮(例如 - 或其他操作)而导致重复输入? 图片点击...)

嵌入重复提交检查通常是一种很好的做法,以避免重复提交。

【讨论】:

  • 有没有办法在服务器端执行这种检查?在 javascript 中,提交按钮在用户点击后会被替换,所以这种情况不应该发生。无论如何,我会重新检查请求日志。
【解决方案3】:

听起来好像您不止一次执行该代码块,您可以发布其余代码吗?处理“put”的代码?

【讨论】:

  • 我已经编辑了代码以添加更多细节。这背后的代码至少分布在 3 个类中,所以我试图删除看起来不相关的大块部分。此外,其中没有 while 或 for 块。
猜你喜欢
  • 2012-05-09
  • 1970-01-01
  • 2014-08-25
  • 2021-07-10
  • 2014-06-12
  • 1970-01-01
  • 1970-01-01
  • 2012-12-06
  • 1970-01-01
相关资源
最近更新 更多