如何检测 2 位管理员是否在 php 中同时更新 table 行
How to detect if 2 admins update a table row at the same time in php
我正在开发的网站有一个管理页面,可以将产品添加到数据库或编辑数据库中的现有产品。如果 2 位管理员想要编辑同一产品,第二位管理员应该会收到一条提醒,该产品已经 been/is 正在更新。当 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);
}
}
编辑:不需要在进行更新后存储旧数据,也不需要存储正在进行的更新(关于这可能是重复的问题)。我想知道的是:如果 admin_1 正在更新一行,而 admin_2 正在尝试更新同一行,而 admin_1 的事务仍在进行中,如何才能我的脚本检测到正在发生这种情况?
考虑以下几点
1)你想要多个管理员编辑不同的产品
2) 你不希望多个管理员编辑同一个产品
3) 在您的代码中,您将产品保存为单个 table。在真正的电子商务网站中,产品信息被分成几个 tables
要实现这一点,您必须设置应用程序范围 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 获取锁,您将得到 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();
?>
这通常是通过添加版本列来完成的,该版本列在每次行更改时更新。在更新行之前,检查该值是否仍然与上次读取该行时相同:
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;
如果出于某种原因交易不可用,另一种方法是使用简单的 compare-and-swap:
UPDATE products SET img_url = ?, version = version + 1 WHERE product_id = ? AND version = 1;
如果更新的行数为零,则该行同时被修改。
可以说,这比 SELECT FOR UPDATE 后跟 UPDATE 稍微快一些。但是,具有冲突版本的行可以实现更丰富的用户反馈。例如,通过作者列,您可以告诉 谁 更新了该行,而不仅仅是它发生了。
我正在开发的网站有一个管理页面,可以将产品添加到数据库或编辑数据库中的现有产品。如果 2 位管理员想要编辑同一产品,第二位管理员应该会收到一条提醒,该产品已经 been/is 正在更新。当 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);
}
}
编辑:不需要在进行更新后存储旧数据,也不需要存储正在进行的更新(关于这可能是重复的问题)。我想知道的是:如果 admin_1 正在更新一行,而 admin_2 正在尝试更新同一行,而 admin_1 的事务仍在进行中,如何才能我的脚本检测到正在发生这种情况?
考虑以下几点 1)你想要多个管理员编辑不同的产品 2) 你不希望多个管理员编辑同一个产品
3) 在您的代码中,您将产品保存为单个 table。在真正的电子商务网站中,产品信息被分成几个 tables
要实现这一点,您必须设置应用程序范围 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 获取锁,您将得到 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();
?>
这通常是通过添加版本列来完成的,该版本列在每次行更改时更新。在更新行之前,检查该值是否仍然与上次读取该行时相同:
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;
如果出于某种原因交易不可用,另一种方法是使用简单的 compare-and-swap:
UPDATE products SET img_url = ?, version = version + 1 WHERE product_id = ? AND version = 1;
如果更新的行数为零,则该行同时被修改。
可以说,这比 SELECT FOR UPDATE 后跟 UPDATE 稍微快一些。但是,具有冲突版本的行可以实现更丰富的用户反馈。例如,通过作者列,您可以告诉 谁 更新了该行,而不仅仅是它发生了。