Bor*_*éry 19 php concurrency phpunit database-deadlocks
我最近在实时应用程序上遇到了一个问题.我意识到我有越来越多的并发异常和数据库锁.
基本上我启动一个事务,它需要一个SELECT
和一个INSERT
在同一个表上提交.
但是因为负载非常繁重,所以每个事务都会锁定表,在大多数情况下,它会如此之快,不会导致任何问题,但锁存器开始等待的时间越来越多.
我通过调整查询来解决这个问题.
虽然,现在,我想用PHPUnit编写一些测试来验证我的修复并避免任何回归.
我无法找到任何关于如何做到这一点的材料.
由于PHP不是多线程的,我不知道如何在单个测试中运行并发查询来验证.
基本上,我希望能够在一次测试中运行多个调用,以确保一切正常.
我知道我可以通过直接查询http服务器并加载整个应用程序来尝试进行一些高级测试,但由于我的问题来自一个独立的库,我宁愿自己测试它.
有任何想法吗?
简短的回答是,使用PHPUnit测试实际数据库上的并发读/写是没有好办法的.它根本不适合这项工作.
但是,测试这个问题的好方法还有一些方面.首先,可以(并且应该)编写代码来处理每个可能的问题.像Postgres这样的数据库系统会在锁和事务问题上立即失败.为了优雅地处理它,我使用看起来像这样的代码(伪代码,也用于回答另一个问题):
begin transaction
while not successful and count < 5
try
execute sql
commit
except
if error code is '40P01' or '55P03'
# Deadlock or lock not available
sleep a random time (200 ms to 1 sec) * number of retries
else if error code is '40001' or '25P02'
# "In failed sql transaction" or serialized transaction failure
rollback
sleep a random time (200 ms to 1 sec) * number of retries
begin transaction
else if error message is 'There is no active transaction'
sleep a random time (200 ms to 1 sec) * number of retries
begin transaction
increment count
Run Code Online (Sandbox Code Playgroud)
然后创建两组测试:一组应确认代码正确处理情况(即单元测试).另一组测试用于环境(即集成/功能测试).
单元测试
我发现在连接数据库的PHPUnit测试中重现这种情况很困难,并且使用真实数据库不适合真正的单元测试.相反,创建抛出各种数据库异常的PDO存根和单元测试.这确认代码按预期工作,但不测试任何真实数据库的并发性,例如:
$iterationCount = 0;
$db->runInTransaction(function() use (&$iterationCount) {
$iterationCount++;
if ($iterationCount === 1) {
$exception = new PDOExceptionStub('Deadlock');
$exception->setCode('40P01');
throw $exception;
}
});
// First time fails, second time succeeds
$this->assertEquals(2, $iterationCount, 'Expected 2 iterations of runInTransaction');
Run Code Online (Sandbox Code Playgroud)
编写一套完整的测试,不连接到DB,但确认逻辑.
集成测试
如您所见,PHPUnit根本不是执行负载测试的正确工具.它不适用于比顺序单元和集成测试更复杂的任何事情.您可以同时运行多个PHPUnit实例,以便为数据库增加更多负载.但是我发现这超出了它的意图,而且它无法帮助您监控数据库中的问题. 因此,我认为没有办法绕过您希望避免的更高级别的测试.
但是可以在不运行完整应用程序的情况下测试您的库.我会创建最简单的应用程序来测试它.它可以有一个或多个连接到数据库的CLI脚本.可以多次生成这些脚本以将负载放在数据库上.或者使用库创建一个简单的网页,并使用任何许多负载测试应用程序来测试它.