我正在使用InnoDB,并具有下表
officeRechNr
year | month | id |
------------------------
2016 | 7 | 2 |
2016 | 6 | 5 |
2016 | 5 | 6 |
我的脚本如下:
从当前年和月中的officeRechNr获取id
将id增加一,并在officeRechNr中更新
回声增加id
因此,如果脚本会一个接一个地执行,我希望:
New id is 3
New id is 4
New id is 5
我假设当我并行执行脚本时,这种行为会有所不同。
这是我的脚本:
$db = new mysqli("localhost","user","pass","db");
$year = date("Y");
$month = date("m");
$stmt = $db->prepare('SELECT zahl FROM officeRechNr WHERE jahr = ? AND monat = ?');
$stmt->bind_param('ii', $year, $month);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
$number = $row['zahl'] + 1;
sleep(20);
$stmt = $db->prepare('UPDATE officeRechNr set zahl = ? WHERE jahr = ? and monat = ?');
$stmt->bind_param('iii',$number, $year, $month);
$stmt->execute();
echo "New id is $number";
我同时执行了两个脚本,所以我假设两个脚本的$number必须等于3,然后它们会休眠20秒。因此,我希望
New id is 3
New id is 3
令我惊讶的是,结果是
New id is 3
New id is 4
我想我需要写锁数据库来实现这个结果,正如用php锁mysql表中所解释的那样。
为什么我没有得到预期的结果?这个脚本总是返回不同的id吗?即使在完全相同的时间执行?
您的Web服务器似乎无法一次执行多个请求。
这意味着,在实践中,即使您在两个不同的时间打开脚本,服务器也不会在第一个请求完成之前执行第二个请求。
时间线如下:
- 打开选项卡1
- 打开选项卡2
- Web服务器从选项卡1接收查询,并开始执行它
- Web服务器开始执行它
- 选项卡1从DB中读取"3"
- Web服务接收来自选项卡2的查询。然而,它没有任何空闲的工作程序,因此它延迟了执行(将其放入等待列表)
- 选项卡1将"4"存储在数据库中
- Webserver完成选项卡1查询的执行,并返回结果
- 既然Web服务器没有任何事情可做,它就会查看它的等待列表,从选项卡2中找到查询,并开始执行它
- 选项卡2从DB中读取"4"
- 选项卡2将"5"存储在数据库中
- 它完成了选项卡2的查询并返回结果
当然,在生产环境中,真正的Web服务器会同时执行多个请求,这意味着您的脚本非常不安全。
更喜欢使用数据库的内置解决方案(AUTO_INCREMENT
用于MySQL,SERIAL
用于PostgreSQL等)。
如果您想要锁定而不是全表锁定,请尝试在事务中使用SELECT...FOR UPDATE
只锁定您选择的行(假设您在jahr、monat上有索引)。看见http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
或者,您根本不需要锁定。你可以使用
UPDATE officeRechNr set zahl = LAST_INSERT_ID(zahl+1) WHERE jahr = ? and monat = ?
阅读的文档http://dev.mysql.com/doc/refman/5.7/en/information-functions.html#function_last-插入id以获取详细信息。
Adam,您的查询似乎不是并行执行的。
记录关键事件的时间并打印出来以确认这一点。
第一条语句执行后:
$stmt = $db->prepare('SELECT zahl ...');
...
$stmt->execute();
$times['select'] = date('H:i:s'); //human-readable format. eg: 11:15:32
然后在服务器上更新值后再次:
$stmt = $db->prepare('UPDATE officeRechNr ...');
...
$stmt->execute();
$times['update'] = date('H:i:s');
我怀疑,如果对每个查询执行print_r($times)
,我们会发现这些查询不是并行发生的,而是按顺序发生的。
如果您使用的是内置的PHP服务器,那么情况肯定是这样的。医生说:
web服务器只运行一个单线程进程,因此PHP如果请求被阻止,应用程序将暂停。