Locking a MySQL INNODB row in PHP

回眸只為那壹抹淺笑 提交于 2019-12-13 04:37:19

问题


I have a table called meta, with two columns name and value.

In a php script, which is called by many clients concurrently, I do this:-

$mysqli->multi_query("SELECT id FROM links WHERE id > (SELECT value FROM meta WHERE name='scan') LIMIT 1000;UPDATE meta SET value=value+1000 WHERE name='scan';");

or this:-

$mysqli->multi_query("SELECT id FROM links WHERE id > (SELECT value FROM meta WHERE name='scan' <b>FOR UPDATE</b>) LIMIT 1000;UPDATE meta SET value=value+1000 WHERE name='scan';");

Unfortunately, this doesn't appear to work as clients are ending up with duplicate id's. The database is heavily loaded and the SELECT takes a few seconds.


回答1:


$mysqli->autocommit(FALSE);
$mysqli->query("BEGIN;");
$mysqli->multi_query("SELECT id FROM links WHERE id > (SELECT value FROM meta WHERE name='scan' FOR UPDATE) LIMIT 1000;UPDATE meta SET value=value+1000 WHERE name='scan';");
$mysqli->commit();

It's a complex issue; locking and transaction levels, but the magic above was the BEGIN statement. Without it, each statement was running in its own transaction level, and the FOR UPDATE lock was being unlocked too early.




回答2:


First try below experiment and then try to map as per your requirement.

Create table:

CREATE TABLE `t1` (                       
  `id` int(11) NOT NULL AUTO_INCREMENT,                 
  `notid` int(11) DEFAULT NULL,                         
  PRIMARY KEY (`id`)                                    
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Then create:

create rowlocking.php

<?php
    require_once('connectvars.php');
    // Connect to the database 
    $dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);

    $query = "START TRANSACTION";
    $data = mysqli_query($dbc, $query);

    $query = "SELECT * FROM t1 WHERE id=5 FOR UPDATE";
    $data = mysqli_query($dbc, $query);

    if (mysqli_num_rows($data) != 0) {
        $row = mysqli_fetch_array($data);
        echo $row['id'];
        echo $row['notid'];
    }

    //$query = "COMMIT";
    //$data = mysqli_query($dbc, $query);
    sleep(10);
    echo "After 10 seconds";
?>

Above script will access a row with id=5 and locks for other transaction till 10 second sleeping time.

create rowlocking1.php

  <?php
    require_once('connectvars.php');
    // Connect to the database 
    $dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);

    $query = "START TRANSACTION";
    $data = mysqli_query($dbc, $query);

    $query = "SELECT * FROM t1 WHERE id=5 FOR UPDATE";
    $data = mysqli_query($dbc, $query);

    if (mysqli_num_rows($data) != 0) {
        $row = mysqli_fetch_array($data);
        echo $row['id'];
        echo $row['notid'];
    }
    //sleep(10);
    //$query = "COMMIT";
    //$data = mysqli_query($dbc, $query);
    //echo "After 10 seconds";
?>

Above script tries to access same row with id=5.

Now if your run script rowlocking and within that 10 second sleeping time if you run rowlocking1 it will not able to access row id=5 till its release by rowlocking. Once 10 sec sleep time over rowlocking will be able to access row id=5.

Try to map this concept with your script you will get innoDB row level locking. Give a comment if you need detailed explanation.




回答3:


Is this what you are looking for?

query("SELECT id FROM links WHERE id > (SELECT value FROM meta WHERE name='scan' LOCK IN SHARE MODE) LIMIT 1000 LOCK IN SHARE MODE;
UPDATE meta SET value=value+1000 WHERE name='scan';");

I'm not sure if this is what you are looking for or not, but if so remember to unlock tables afterward when/where necessary and make sure that the account you are using has LOCK privileges.

MySQL Documentation on INNODB READ LOCKS



来源:https://stackoverflow.com/questions/4976487/locking-a-mysql-innodb-row-in-php

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!