Php的Mysqli以随机顺序执行语句(非常奇怪)

时间:2011-11-12 13:13:16

标签: php mysql mysqli prepared-statement

编辑:我很抱歉,我发现了这个错误而且非常愚蠢。 (见答案)

我似乎有一个非常奇怪的问题。我有一个使用第三方授权登录的网站。我的用户使用两个社交网络:Facebook和Vkontakte(俄语模拟)。

登录时,我在数据库中搜索具有传递的社交ID的用户(这取决于所选择的社交网络,FB或VK中的用户ID,我有两个不同的列)并获取它。

如果用户同时拥有FB和VK中的帐户并且已通过这两个帐户登录,则他或她现在在我的网站上有两个单独的帐户。但是,他可以通过一个社交网络登录(这将是他的主帐户)并将'user_bind'功能与另一个社交网络一起使用,将他们加入其中。

此功能查找用户的另一个帐户,并将数据库中的所有数据重新链接到主帐户。然后删除其他帐户并将其社交ID添加到主帐户,以便现在用户可以通过两个社交网络登录。 Social id列自然有一个UNIQUE索引。

但是,当脚本执行时,它似乎执行UPDATE,它在DELETE语句之前添加社交ID ,这将删除旧用户。这会产生错误,因为它会尝试添加现有的社交ID(因为旧用户仍在那里)。

当我在脚本执行后检查数据库时,旧用户不见了,所以我猜这意味着DELETE语句确实已执行,但有延迟,其中执行了其他语句。 MySQL Workbench的日志确认了这一点,但我不确定它是否可靠。

我的问题如何确保在执行脚本的其余部分之前实际执行了DELETE(或任何其他MySQL语句)?为什么会发生这种情况呢?

这是充分评论的代码(尽管我很乐意接受一个没有代码的答案,只是解释了原理)。

user_bind函数:

function user_bind($eSourceType)
    {
    //$eSourceType can be 'fb' or 'vk', depending on the social network of the secondary account

    $usrMe=get_gl_me(); //gets the user's account, through which he is logged in - the master account
    if ($eSourceType=='fb') //if the social network that we are binding this account to is Facebook
        {
        $vSidName='facebook_id'; //name of the column which contains the social id
        if (!$usrMe->get_private_property("facebook_id") & $usrMe->get_private_property("vkontakte_id") ) //check if the master account really doesn't have facebook_id set
            {
            $fb=get_facebook();//gets facebook object (from FB PHP SDK)
            $sid=$fb->getUser();//gets user's id in facebook (social id)
            }
        else
            {
            error("The account has facebook_id set");
            }
        }
    elseif($eSourceType=='vk')//same as before, but the id is fetched through $_GET, not object
        {
        $vSidName='vkontakte_id';
        if ($usrMe->get_private_property("facebook_id") & !$usrMe->get_private_property("vkontakte_id") ) //check if it's the right account
            {
            $sid=$_GET['uid'];
            }
        else
            {
            error("The account has vkontakte_id set");
            }
        }

    if(!$sid) //if we couldn't retrieve the social id
        {
        error("Can't bind: \$sid not set.");
        }

    $idNew=$usrMe->get_id();//get id (database id) of the master account

    $usrOld=fetch_user_by_sid($sid, $eSourceType, true); //fetches the 'user' object by the social id we retrieved before
    if ($usrOld)//if there is a user with this social id (if there is a secondary account)
        {
        $idOld=$usrOld->get_id();//get id of the secondary account
        $tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments"); //get tables in which we have to relink users
        foreach($tblsRelink as $tbl)
            {
            //update set users_idusers to userid
            $sp=new Statement_Parameter; //this is a class from PHP.com: http://php.net/manual/en/mysqli-stmt.bind-param.php. It allows to bind variables to the prepared statement in MySQLi without much pain
            $query="UPDATE $tbl SET users_idusers=" . db_stmt_operands($idNew, $sp, 'idNew') . " WHERE users_idusers=". db_stmt_operands($idOld, $sp, 'idOld'); //db_stmt_operands inserts question marks in the query, while binding the variables through Statement_Parameter
            $affected_rows=db_submit($query, $sp);//see below for the db_submit() function explanation
            }

        //delete old user
        $sp=new Statement_Parameter; //clear Statement_Parameter

        $query="DELETE FROM users WHERE idusers=" . db_stmt_operands($idOld, $sp, 'idOld');
        $affected_rows=db_submit($query, $sp);
        echo "<br>affected: $affected_rows<BR>"; //this actually returns 1

        //lets see if the user was actually deleted
        $usrTest=fetch_user_by_sid($sid, $eSourceType, true); //fetch the user by the social id
        if($usrTest) //if a user is fetched
            {
            debug_array($usrTest); //custom implementation of print_r
            error("User still exsists. Oh no.");//it always does
            }
        }


    $usrMe->set_private_property($vSidName, $sid);//sets the property 'facebook_id' or 'vkontakte_id' to the social id that we got in the beginning
    $usrMe->update();//UPDATE statement, which brings the object's properties in the database up to date (in our case: adds the social id)
    //the UPDATE statement doesn't execute because the old user is still there
    }

db_submit函数:

function db_submit($query, $sp=NULL)
    {
    $mysqli = db_connect(); //fetches PHP MySQLi object
    if ($stmt = $mysqli->prepare($query)) //if the statement is successfully prepared
        {
        if($sp)//if there is a Statement_Parameter passed
            {
            $sp->Bind_Params($stmt); //bind parameters from SP
            }

        if($stmt->execute())//try to execute the statement
            {
            //on success
            if ($mysqli->insert_id) //if this was an INSERT
                {
                return $mysqli->insert_id;
                }
            else //if this was DELETE or UPDATE
                {
                return $mysqli->affected_rows;
                }
            }
        else
            {
            //on failure
            error("Could not submit: could not execute statement. Query: $query." . $stmt->error); //this kills the script
            }
        }
    else
        {
        error("Could not submit. Query: $query." . $mysqli->error); 
        }
    }

1 个答案:

答案 0 :(得分:1)

事情是:private_properties(包括社交ID)或对象'us​​er'存储在一个单独的表('users_private')中,该表通过外键链接到主表('users')。 / p>

我将'users_private'表包含在需要重新链接的表数组中:

        $tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments"); 

这导致'users_private'中的记录显示旧用户正在重新链接到新用户(现在有2条记录 - 我不会让这个字段变成UNIQUE的鲁莽)。因此,当旧用户被删除时,其关联的“users_private”记录不会,因为它现在已链接到新用户。当然,尝试添加社交ID会产生错误,因为该ID已存在,与旧用户重新链接。

这可以通过

来防止
  1. 多思考一下我在做什么(为什么我认为'users_private'有资格进行重新连接的表?)
  2. 更仔细的数据库结构(如果字段应该是唯一的 - 创建一个UNIQUE键!)
  3. 或两者都更好。