문제

The goal is to have a PHP script with a certain section of the code that can only be executed by one thread/process at a time.

Some restrictions:

  • semaphores not available on my system
  • The manual mentions that flock() cannot be relied on in multi-threaded servers, so flock() is out. (confirmed this)

So thought it would be possible (why not?) to use MySQL for synchronization, but I'm getting unexpected results.

To test, I have two browser tabs open with the script test.php

mysql_connect($dbhost, $dbusername, $dbpassword); mysql_select_db($database_name);

$sql = "SELECT my_val FROM my_table WHERE my_var='status' "; $result = mysql_query($sql)or die(mysql_error()); $row = mysql_fetch_array($result, MYSQL_ASSOC); echo "status is:".$row['my_val']."
\n"; mysql_free_result($result);

if ($row['my_val']=='RUNNING') die ('process is already running!');

// entering critical section

$sql = "UPDATE my_table SET my_val='RUNNING' WHERE my_var='status' "; mysql_query ($sql);

sleep(10); // do some work here. echo 'finished work
';

// leave critical section. let the others know $sql = "UPDATE my_table SET my_val='NOT_RUNNING' WHERE my_var='status' "; mysql_query ($sql);

The table is:

CREATE TABLE `my_table` (
  `my_var` varchar(255) NOT NULL default '',
  `my_val` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`my_var`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `my_table`
VALUES (
'status', 'NOT_RUNNING'
) 

I go to the first browser tab and access the script. I wait 5 seconds, go to the other tab and access the script so that both scripts run in parallel. I expect the first tab to wait, and the 2nd tab should exit after the 1st query. However, both tabs wait on the sleep() line. This means that the first query always returns 'NOT_RUNNING' .

Some odd things:

  • When I repeat the above experiment, I run both tabs in FireFox, and then in a 3rd different browser type, say Chrome, then it works! (status is set to RUNNING while the sleeping, script exits early when status is RUNNING )
  • When I repeat the above experiment using two different command line windows, and run the script from the command line, it works!
  • I check phpMyAdmin while it is waiting, status gets updated correctly.

I have tried everything, locking the table, transactions, SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED, SET autocommit=1;, yet always the same result.

Does anyone know what is happening here? Is there any good solution for this problem?

Systems tested: - Win, MySQL 5.0, php 5.3 - Linux, MySQL 5.0.91-community-log, php 5.2.12

I'm totally stuck here, thanks for taking a look!


UPDATE:

Thanks for submitting your answers - I still cannot solve this problem. Here is the code with GET_LOCK() and session_write_close() as suggested: I have also tried row level locking, transactions and different isolation levels. Perhaps it cannot be done?

session_write_close();

mysql_connect($dbhost, $dbusername, $dbpassword);
mysql_select_db($database_name);

$sql = "CREATE TABLE IF NOT EXISTS `my_table`  (
  `my_var` varchar(255) NOT NULL default '',
  `my_val` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`my_var`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
";
mysql_query($sql)or die(mysql_error());

mysql_query ("SELECT get_lock('my_lock', 100)");

$sql = "SELECT my_val FROM `my_table` WHERE `my_var`='status' ";
$result = mysql_unbuffered_query($sql)or die(mysql_error());
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "status is:".$row['my_val']."
\n"; mysql_free_result($result); if ($row['my_val']=='RUNNING') die ('process is already running!'); // entering critical section $sql = "UPDATE `my_table` SET `my_val`='RUNNING' WHERE `my_var`='status' "; mysql_query ($sql); sleep(10); // do some work here. echo 'finished work
'; // leave critical section. let the others know //$sql = "UPDATE `my_table` SET `my_val`='NOT_RUNNING' WHERE `my_var`='status' "; $sql = "REPLACE INTO `my_table` (`my_var`,`my_val`) VALUES ('status', 'NOT_RUNNING')"; mysql_query ($sql); $result = mysql_query($sql)or die(mysql_error()); mysql_query ("SELECT release_lock('my_lock')"); die();
도움이 되었습니까?

해결책

The second browser window is not hanging on the sleep command. It's blocking because sessions are blocking (and hence it's trying to open the same session). You can close the current session if you don't need it anymore (and hence don't want it to block) with session_write_close...

다른 팁

Mysql has a function called GET_LOCK.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top