使用模型工厂时的MySQL锁等待超时问题

23

我的一个测试类中生成了一个非常小的数据集,我尝试在setUp()方法中生成数据,但每个测试都导致锁等待超时。将此代码从setUp()方法中移出并放入自己的方法中有所帮助。这意味着并非每个测试都会抱怨锁。

我正在使用Laravel 5.2中的DatabaseTransactions特性,以便在运行每个测试用例之前重置数据库。

在我拥有的每个测试用例中,我都会调用以下代码来获取模拟数据。

$data = self::getRandomCommunityWithAssociatedData();

实际方法只会为创建的社区生成少量社区对象和用户对象。

public static function getRandomCommunityWithAssociatedData()
{
    self::$communities = factory(\Community::class, self::COMMUNITIES_TO_CREATE)->create()->each(function ($community) {
        self::$users[$community->id] = factory(User::class, self::USERS_TO_CREATE)->create()->each(function (\User $user) use ($community) {
            $user->community()->associate($community);
        });

        self::$admins[$community->id] = factory(User::class, 'superadmin', self::ADMINS_TO_CREATE)->create()->each(function (\User $admin) use ($community) {
            $admin->community()->associate($community);
        });
    });

    $community = self::$communities[mt_rand(0, count(self::$communities) - 1)];

    return ['community' => $community, 'users' => self::$users[$community->id], 'admins' => self::$admins[$community->id]];
}

该方法中使用了几个常量,它们用于确定要创建多少个每个对象。目前,我正在为每个社区实例创建2个社区、3个用户和2个管理员。

锁等待超时不可预测,一次运行可能发生在第一个测试用例上,另一次运行可能发生在第5个测试用例上。

我尝试将MySQL等待锁的时间增加到500秒,但仍然会出现超时情况。增加这个时间真的不是一个选项,因为测试需要能够在所有环境中运行。

使用Laravel 5.2中的DatabaseTransactions特性,为什么使用如此小的数据集时可能会出现锁等待超时的情况?有什么想法吗?

1) UserEmailNotificationsTest::testActiveAdminReceivesNewCommentEmailNotification
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction (SQL: insert into `communities` (`

上述执行的查询是在社区表上进行的简单插入操作,没有涉及任何复杂的子查询或其他数据生成操作。

启用查询日志后,结果。

       30 Query START TRANSACTION
       30 Query SAVEPOINT trans2
       30 Prepare   insert into `communities` (`viddler_id`, `domain`, `subdomain`, `active`, `max_seats`, `created_at`, `updated_at`, `assignment_seats`, `general_settings`, `access_settings`, `branding_settings`) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
       30 Execute   insert into `communities` (`viddler_id`, `domain`, `subdomain`, `active`, `max_seats`, `created_at`, `updated_at`, `assignment_seats`, `general_settings`, `access_settings`, `branding_settings`) values ('74', 'gaylord.com', 'qui', '1', '24', '2016-03-30 16:25:45', '2016-04-04 02:27:04', '23', 'a:11:{s:5:\"title\";s:0:\"\";s:11:\"description\";s:0:\"\";s:10:\"meta_title\";s:0:\"\";s:16:\"meta_description\";s:0:\"\";s:13:\"meta_keywords\";s:0:\"\";s:17:\"thumbnail_display\";s:0:\"\";s:18:\"group_nomenclature\";s:8:\"Channels\";s:13:\"home_template\";s:0:\"\";s:11:\"api_version\";s:1:\"2\";s:8:\"language\";s:2:\"en\";s:21:\"use_nested_navigation\";i:0;}', 'a:10:{s:12:\"restrictions\";s:10:\"Restricted\";s:17:\"auto_registration\";i:0;s:14:\"default_active\";i:0;s:12:\"oauth_google\";i:0;s:14:\"oauth_facebook\";i:0;s:14:\"oauth_linkedin\";i:0;s:16:\"oauth_reg_google\";i:0;s:18:\"oauth_reg_facebook\";i:0;s:18:\"oauth_reg_linkedin\";i:0;s:11:\"lti_enabled\";i:0;}', 'a:14:{s:13:\"contact_email\";s:0:\"\";s:17:\"contact_link_text\";s:0:\"\";s:9:\"logo_file\";s:0:\"\";s:14:\"carousel_items\";s:0:\"\";s:11:\"html_header\";s:0:\"\";s:16:\"footer_copyright\";s:45:\"© 2015 Viddler Inc. All Rights Reserved.\";s:19:\"footer_privacy_link\";s:37:\"http://www.viddler.com/privacy-policy\";s:17:\"footer_terms_link\";s:35:\"http://www.viddler.com/terms-of-use\";s:14:\"help_link_text\";s:4:\"Help\";s:9:\"help_link\";s:0:\"\";s:7:\"color_1\";s:7:\"#ffffff\";s:7:\"color_2\";s:7:\"#2C333C\";s:7:\"color_3\";s:7:\"#2C333C\";s:7:\"color_4\";s:7:\"#60a1d7\";}')
160404  9:30:25    30 Close stmt
       21 Query ROLLBACK
       21 Quit
       22 Query ROLLBACK
       22 Quit
       23 Query ROLLBACK
       23 Quit
       24 Query ROLLBACK
       24 Quit
       25 Query ROLLBACK
       25 Quit
       26 Query ROLLBACK
       26 Quit
       27 Query ROLLBACK
       27 Quit
       28 Query ROLLBACK
       28 Quit
       29 Query ROLLBACK
       29 Quit
       30 Query ROLLBACK
       30 Quit

启用查询日志并查看哪些查询导致了死锁。您还应将超时设置恢复到正常水平。 - Daniel W.
插入到 communities 表格的操作失败了。我已经更新了问题,以显示来自 mysql 查询日志中的数据。 - Joseph Crawford
1
虽然没有回答你的问题,但我建议只在测试时转换到SQLite。这将大大加快测试运行时间。 - Mysteryos
我认为这通常是由多个查询同时运行相同的表所致。你能在每次调用后结束事务吗?如果测试正在运行时,你登录到数据库并调用 show open tables where in_use>0; 会发生什么?如果你终止测试进程会发生什么? - cwallenpoole
1
+1 对于 @Mysteryos 在 sqlite 评论中的回复。你可以设置你的 PHPUnit 使用 SQLite 进行测试并存储在内存中,从而显著提高性能。请参考 https://vimeo.com/151390908 - samrap
尝试使用持久连接到MySQL。 - M Rostami
1个回答

2
因此,增加锁等待超时时间不是最佳实践;最佳实践是捕获错误并进行恢复。 官方文档建议如下:
死锁和锁等待超时在繁忙的服务器上很常见,应用程序需要意识到它们可能会发生并通过重试来处理它们。在事务期间对数据进行第一次更改和提交之间尽可能地减少工作量,以便锁定时间最短且行数最少。有时,在不同的事务之间分割工作可能是实际且有帮助的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接