有没有办法让这个数据库锁更有效?

时间:2014-10-15 11:05:17

标签: php mysql

我编写了这段代码,以确保两个用户无法同时编辑同一个表的同一行。我遇到的唯一问题是它连接数据库3次,一次添加新锁,一次检查它是该行的唯一锁,一次是删除锁还是检索数据用户编辑。我真的不喜欢这个,但这是我能想象的唯一方式。

有没有什么方法可以提高效率?

<?php
$CountryID = $_GET["CountryID"];
$userID = $_SESSION["userID"];
$currentTime = date('Y-m-d H:i:s');
try{
    include_once 'PDO.php';
    //Adds a new lock into the database.
    $dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
    $sth = $dbh->prepare('INSERT INTO lock (UserID, TableID, RowID) VALUES (?, ?, ?)');
    $sth->execute(array($userID,1,$CountryID));
    $insertedID = $dbh->lastInsertId();


    //Checks to see if there is more than one lock for this table/row. If there is more than 1 row, it will check to make sure that all OTHER rows are older than 5 minutes old incase of broken locks.
    $sth = $dbh->prepare('SELECT * from lock where TableID = ? AND RowID = ?');
    $sth->execute(array(1,$CountryID));
    $rowCount = $sth ->rowCount();
    $locked = false;
    if ($rowCount >1 ){
        foreach($sth as $row){
            if ($row['LockID'] != $insertedID AND (abs(strtotime($currentTime) - strtotime($row['Date']))) < 300){ 
                $locked = true;
                break;
            }
        }
    }
    if ($locked){
        //Delete the lock we put in first, and tell the user that someone is already editing that field. 
        $sth = $dbh->prepare('DELETE from lock where LockID = ?');
        $sth->execute(array($insertedID));
        echo "Row is currently being edited.";
    }else{
        //Don't delete the lock, and get data from the country table.
        echo "Row isn't being edited.";
        $sth = $dbh->prepare('SELECT * from country where CountryID = ?');
        $sth->execute(array($CountryID));
    }
}catch (PDOException $e){
    echo "Something went wrong: " . $e->getMessage();
}
$dbh = null;
?>

1 个答案:

答案 0 :(得分:1)

看起来你想要一个用于建议持久锁定的方案。也就是说,您希望将锁定保持相对较长的时间 - 比DBMS事务可以合理地期望从Web应用程序执行的时间更长。我称之为&#34;咨询&#34;因为它不是强制性的&#34;在DBMS强制执行它的意义上。

你非常接近。我建议您使用复合主键lock定义(TableID, RowID)表。这样,尝试将重复记录插入该表将失败。忘掉LockID。你不需要它。 UserID非常有用,因为它会为您提供诊断问题的提示。

然后,要设置锁定(在表1的示例中,行$ CountryID),您将执行以下操作:

$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); /* only once*/

$sth = $dbh->prepare('INSERT INTO lock (UserID, TableID, RowID) VALUES (?, ?, ?)');
$locked = true;
try {
    $sth->execute(array($userID,1,$CountryID));
}
catch (PDOException $e) { 
    $locked = false;
}

这很好,因为您可以通过将$ locked设置为false来处理重复键错误(完整性约束违规)。当$ locked为false时,您知道其他人已经锁定,您无法获得锁定。它也很好,因为它具有竞争条件。如果有两个用户正在争夺同一个锁,那么其中一个肯定会赢,而另一个肯定会失败。

如果要释放锁定,请以类似方式执行删除操作。

$sth = $dbh->prepare('DELETE FROM lock WHERE TableID = ? AND RowID = ?');
$it_was_locked = false;
$sth->execute(array(1,$CountryID));
if ($sth->rowCount() > 0) {
    $it_was_locked = true;
}

此处,变量$ it_was_locked可让您知道锁是否已经到位。在任何情况下,运行此命令后,锁定将被清除。

还有一件事。为了系统的完整性,请考虑定义锁定超时。也许它应该是10秒,也许应该是10个小时:这取决于您的应用程序的用户体验需求。如果人们开始但没有完成交易,超时将使您的应用程序不会被卡住。

然后,将locktime列放入lock表中,并自动将当前时间放入其中。您可以在表定义中使用这样的行来执行此操作。

locktime TIMESTAMP DEFAULT CURRENT_TIMESTAMP

然后,无论何时插入锁定,首先释放所有已超时的锁定,如此。

$stclean = $dbh->prepare('DELETE FROM lock WHERE locktime < NOW() - 10 SECOND');
$sth = $dbh->prepare('INSERT INTO lock (UserID, TableID, RowID) VALUES (?, ?, ?)');
$locked = true;
$stclean->execute();
if ($stclean->rowCount() > 0) {
     /* some timed-out locks were released; make an error log entry or whatever */
}
try {
    $sth->execute(array($userID,1,$CountryID));
}
catch (PDOException $e) { 
    $locked = false;
}

这种事情使得可维护的锁定方案成为可能。当你进行系统测试时,你可以期待看看这个锁定表,试图找出哪个模块忘记了锁定以及哪个模块忘记释放它。