如何检测2位管理员是否同时在php中更新表行

时间:2019-05-24 12:10:03

标签: php sql admin innodb

我正在开发的网站上有一个管理页面,用于将产品添加到数据库或编辑数据库中的现有产品。如果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在尝试进行事务处理时正在尝试更新同一行,那么我的脚本如何检测到这种情况呢?

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();
?>