在并行系统中并发问题永远不可忽视。尽管PHP语言原生没有提供多线程机制,那并不意味着所有的操作都是线程安全的。尤其是在操作诸如订单、支付等业务系统中,更需要注意操作数据库的并发问题。
接下来我通过一个案例分析一下PHP操作数据库时并发问题的处理问题。
首先,我们有这样一张数据表:
1 mysql> select * from counter;
2 +----+-----+
3 | id | num |
4 +----+-----+
5 | 1 | 0 |
6 +----+-----+
7 1 row in set (0.00 sec)
这段代码模拟了一次业务操作:
1 <?php 2 function dummy_business() { 3 $conn = mysqli_connect('127.0.0.1', 'public', 'public') or die(mysqli_error()); 4 mysqli_select_db($conn, 'test'); 5 for ($i = 0; $i < 10000; $i++) { 6 mysqli_query($conn, 'UPDATE counter SET num = num + 1 WHERE id = 1'); 7 } 8 mysqli_close($conn); 9 } 10 11 for ($i = 0; $i < 10; $i++) { 12 $pid = pcntl_fork(); 13 14 if($pid == -1) { 15 die('can not fork.'); 16 } elseif (!$pid) { 17 dummy_business(); 18 echo 'quit'.$i.PHP_EOL; 19 break; 20 } 21 } 22 ?> |
上面的代码模拟了10个用户同时并发执行一项业务的情况,每次业务操作都会使得num的值增加1,每个用户都会执行10000次操作,最终num的值应当是100000。
运行这段代码,num的值和我们预期的值是一样的:
1 mysql> select * from counter;
2 +----+--------+
3 | id | num |
4 +----+--------+
5 | 1 | 100000 |
6 +----+--------+
7 1 row in set (0.00 sec)
这里不会出现问题,是因为单条UPDATE语句操作是原子的,无论怎么执行,num的值最终都会是100000。
然而很多情况下,我们业务过程中执行的逻辑,通常是先查询再执行,并不像上面的自增那样简单:
1 <?php 2 function dummy_business() { 3 $conn = mysqli_connect('127.0.0.1', 'public', 'public') or die(mysqli_error()); 4 mysqli_select_db($conn, 'test'); 5 for ($i = 0; $i < 10000; $i++) { 6 $rs = mysqli_query($conn, 'SELECT num FROM counter WHERE id = 1'); 7 mysqli_free_result($rs); 8 $row = mysqli_fetch_array($rs); 9 $num = $row[0]; 10 mysqli_query($conn, 'UPDATE counter SET num = '.$num.' + 1 WHERE id = 1'); 11 } 12 mysqli_close($conn); 13 } 14 15 for ($i = 0; $i < 10; $i++) { 16 $pid = pcntl_fork(); 17 18 if($pid == -1) { 19 die('can not fork.'); 20 } elseif (!$pid) { 21 dummy_business(); 22 echo 'quit'.$i.PHP_EOL; 23 break; 24 } 25 } 26 ?> |