我正在开发的网站上有一个管理页面,用于将产品添加到数据库或编辑数据库中的现有产品。如果2位管理员想要编辑同一产品,则第二位管理员应收到有关该产品已经/正在更新的警报。当2位管理员试图编辑同一产品时,如何检测php / sql?
我真的没有尝试过任何东西,因为我不知道尝试什么或从哪里开始。
这是我的函数在数据库中更新产品的外观:
//I know this is a VERY unsafe function, this website will never go online!
FUNCTION updateProduct($data) {
include_once 'dbconn.php';
try {
$conn = connectToDb();
if ($data['imageChanged'] == false) {
$query = "SELECT img_url FROM products WHERE product_id=".$data['productId'];
$result = mysqli_query($conn, $query);
if ($result == false) {
throw new Exception('Failed to execute: '. $query . 'Error: '. mysqli_error($conn));
}
$imgUrl = mysqli_fetch_row($result)[0];
} else {
$imgUrl = $data['image'];
}
$query2 = "img_url='".$imgUrl."'";
$query = "UPDATE products
SET product_name='".$data['productName']."', product_desc='".$data['productDesc']."', price=".$data['price'].", ". $query2 . "
WHERE product_id=".$data['productId'];
$result = mysqli_query($conn, $query);
if ($result == false) {
throw new Exception('Failed to execute: '. $query . 'Error: '. mysqli_error($conn));
}
} finally {
mysqli_close($conn);
}
}
edit:不需要在更新完成后存储旧数据,也不需要存储正在进行的更新(关于这个问题可能与之重复)。我想知道的是:如果admin_1正在更新一行,而admin_2在尝试进行事务处理时正在尝试更新同一行,那么我的脚本如何检测到这种情况呢?
答案 0 :(得分:2)
这通常是通过添加每次行更改时都会更新的版本列来完成的。在更新该行之前,请检查该值是否与上次读取该行的时间相同:
SELECT img_url, version FROM products WHERE product_id = ?;
-- Suppose this returns ('https://example.com/foo.jpg', 1)
-- Remember that the current version is 1.
稍后:
BEGIN;
SELECT version FROM products WHERE product_id = ? FOR UPDATE;
-- Check if version is still 1. If it's not, someone modified the row (execute ROLLBACK in this case).
-- Otherwise:
UPDATE products SET img_url = ?, version = version + 1 WHERE product_id = ?;
COMMIT;
如果出于任何原因无法进行交易,则可以选择使用简单的比较和交换:
UPDATE products SET img_url = ?, version = version + 1 WHERE product_id = ? AND version = 1;
如果更新的行数为零,则该行已同时被修改。
可以说,这比SELECT FOR UPDATED和UPDATE快一些。但是,如果该行的版本相互冲突,则会产生更丰富的用户反馈。例如,在作者列中,您可以告诉谁更新了该行,而不仅是它发生了。
答案 1 :(得分:0)
考虑以下几点 1)您希望多个管理员编辑不同的产品 2)您不希望多个管理员编辑同一产品
3)在您的代码中将产品保存为单个表格。在一个真实的电子商务站点中,产品信息被分为几个表
要实现此目的,您必须设置应用程序范围的mysql锁
mysql> select get_lock('product_id_12345', 5);
+---------------------------------+
| get_lock('product_id_12345', 5) |
+---------------------------------+
| 1 |
+---------------------------------+
1 row in set (0.00 sec)
上面的代码使用product_id设置了锁定,如果其他某个连接尝试使用相同的product_id获取锁定,则u的响应将为0-表示其他一些用户正在更新相同的product_id
mysql>
mysql> select IS_FREE_LOCK('product_id_12345');
+----------------------------------+
| IS_FREE_LOCK('product_id_12345') |
+----------------------------------+
| 0 |
+----------------------------------+
1 row in set (0.00 sec)
mysql>
mysql> select IS_USED_LOCK('product_id_12345');
+----------------------------------+
| IS_USED_LOCK('product_id_12345') |
+----------------------------------+
| 46 |
+----------------------------------+
1 row in set (0.00 sec)
mysql>
mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
| 46 |
+-----------------+
1 row in set (0.00 sec)
示例算法
<?php
ini_set('display_errors', 1);
function updateProduct() {
$user = 'root';
$pass = 'xxx';
$DB = 'test';
$host = 'localhost';
try
{
$conn = new mysqli($host, $user, $pass, $DB);
$data['productId'] = 'product_id_12345';
$data['productName'] = 'test';
$data['productDesc'] = 'testing';
$isLockFree = 'select IS_USED_LOCK("'.$data['productId'].'") ';
$isLockFreeResult = mysqli_query($conn, $isLockFree);
$row=mysqli_fetch_row($isLockFreeResult);
if(empty($row[0]))
{
$lock = 'select get_lock("'.$data['productId'].'")';
$result = mysqli_query($conn, $lock);
echo 'lock established';
$query = "UPDATE products
SET product_name='".$data['productName']."',
product_desc='".$data['productDesc']."',
price=".$data['price']."
WHERE
product_id=".$data['productId'];
$result = mysqli_query($conn, $query);
if ($result == false)
{
throw new Exception('Failed to execute: '. $query . 'Error: '. mysqli_error($conn));
}
}
else
{
echo 'sorry! could not lock. somebody is updating the product info';
}
}
finally
{
mysqli_close($conn);
}
}
updateProduct();
?>