简体   繁体   中英

How to detect if 2 admins update a table row at the same time in php

The website i'm developing has an admin page to add products to the database or edit existing products in the database. If 2 admins want to edit the same product the second admin should get an alert that the product has already been/is now being updated. How could I detect in php/sql when 2 admins try to edit the same product?

I haven't really tried anything because I have no idea what to try or where to start.

here's how my function looks for updating a product in the database:

//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: storing the old data after an update is made is not needed nor is storing the updates being made(regarding the question this might be a duplicate of). The thing I would like to know is: if admin_1 is updating a row, and admin_2 is trying to update the same row at the time admin_1's transaction is still ongoing, how can my script detect that this is happening?

This is usually done by adding a version column that is updated every time the row changes. Before updating the row, check if the value is still the same as when the row was last read:

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.

Later:

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;

If for whatever reason transactions are not available, an alternative is to use a simple compare-and-swap:

UPDATE products SET img_url = ?, version = version + 1 WHERE product_id = ? AND version = 1;

If the number of updated rows is zero, the row has been modified in the meantime.

Arguably, this is slightly quicker than the SELECT FOR UPDATED followed by UPDATE. However, having the conflicting version of the row enables much richer user feedback. With an author column you can tell who updated the row, for instance, not just that it happened.

consider below points 1) u want multiple admin to edit different products 2) u do not want multiple admin to edit same product

3) in your code u saved product as a single table. In a real e commerce site product information is split into several tables

to achieve this u have to set up application wide mysql locks

mysql> select get_lock('product_id_12345', 5);
+---------------------------------+
| get_lock('product_id_12345', 5) |
+---------------------------------+
|                               1 |
+---------------------------------+
1 row in set (0.00 sec)

above code sets up a lock using a product_id, if some other connection tries to get lock using the same product_id, u will get 0 as response - indicating that some other user is updating the same 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)

SAMPLE ALGORITHM

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM