我编写了这段代码,以确保两个用户无法同时编辑同一个表的同一行。我遇到的唯一问题是它连接数据库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;
?>
答案 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;
}
这种事情使得可维护的锁定方案成为可能。当你进行系统测试时,你可以期待看看这个锁定表,试图找出哪个模块忘记了锁定以及哪个模块忘记释放它。