多EXISTS语句致MySQL查询挂起的原因排查与优化咨询
全国房产EAV模型查询性能问题
背景
我正在实现实体-属性-值(EAV)模型存储全国房产数据,每日更新:涉及1.3亿套房产,将存储数十亿条属性值。我们需要支持任意属性的搜索,因此采用EAV模型以便为每个属性值建立索引并进行搜索(而非使用单张大表及大量索引)。我使用Ruby on Rails框架,通过其各类宏构建查询。
数据库Schema
CREATE TABLE `properties` ( `id` bigint NOT NULL AUTO_INCREMENT, `aid` int NOT NULL, `deleted_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_properties_on_deleted_at_and_aid` (`deleted_at`,`aid`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3; CREATE TABLE `property_attributes` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL, `value_type` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_property_attributes_on_name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3; CREATE TABLE `property_value_booleans` ( `id` bigint NOT NULL AUTO_INCREMENT, `aid` int NOT NULL, `attribute_id` bigint NOT NULL, `value` tinyint(1) DEFAULT NULL, `deleted_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_pvb_on_daav` (`deleted_at`,`aid`,`attribute_id`,`value`), KEY `index_pvb_on_dav` (`deleted_at`,`attribute_id`,`value`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3; CREATE TABLE `property_value_dates` ( `id` bigint NOT NULL AUTO_INCREMENT, `aid` int NOT NULL, `attribute_id` bigint NOT NULL, `value` date DEFAULT NULL, `deleted_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_pvd_on_daav` (`deleted_at`,`aid`,`attribute_id`,`value`), KEY `index_pvd_on_dav` (`deleted_at`,`attribute_id`,`value`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3; CREATE TABLE `property_value_numerics` ( `id` bigint NOT NULL AUTO_INCREMENT, `aid` int NOT NULL, `attribute_id` bigint NOT NULL, `value` decimal(18,6) DEFAULT NULL, `deleted_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_pvn_on_daav` (`deleted_at`,`aid`,`attribute_id`,`value`), KEY `index_pvn_on_dav` (`deleted_at`,`attribute_id`,`value`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3; CREATE TABLE `property_value_strings` ( `id` bigint NOT NULL AUTO_INCREMENT, `aid` int NOT NULL, `attribute_id` bigint NOT NULL, `value` varchar(255) DEFAULT NULL, `created_at` datetime DEFAULT NULL, `deleted_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_pvs_on_daav` (`deleted_at`,`aid`,`attribute_id`,`value`), KEY `index_pvs_on_dav` (`deleted_at`,`attribute_id`,`value`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
问题查询
SELECT COUNT(*) FROM `properties` WHERE `properties`.`deleted_at` IS NULL AND (exists (SELECT 1 FROM `property_value_strings` as t_74653 WHERE t_74653.`deleted_at` IS NULL AND t_74653.`attribute_id` = 48 AND t_74653.`value` = 'NC' AND (t_74653.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_a9e53 WHERE t_a9e53.`deleted_at` IS NULL AND t_a9e53.`attribute_id` = 14 AND t_a9e53.`value` = 'Wake' AND (t_a9e53.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_a225b WHERE t_a225b.`deleted_at` IS NULL AND t_a225b.`attribute_id` = 163 AND t_a225b.`value` IN ('181', '366', '369', '378', '385', '386', '388') AND (t_a225b.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_booleans` as t_2b45b WHERE t_2b45b.`deleted_at` IS NULL AND t_2b45b.`attribute_id` = 2 AND t_2b45b.`value` = TRUE AND (t_2b45b.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_dates` as t_54497 WHERE t_54497.`deleted_at` IS NULL AND t_54497.`attribute_id` = 174 AND t_54497.`value` BETWEEN '2017-05-16' AND '2017-06-15' AND (t_54497.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_numerics` as t_a525e WHERE t_a525e.`deleted_at` IS NULL AND t_a525e.`attribute_id` = 175 AND t_a525e.`value` BETWEEN 200000.0 AND 1000000.0 AND (t_a525e.`aid` = `properties`.`aid`))) AND # Just checking for existence of these values, not concerned with actual value. (exists (SELECT 1 FROM `property_value_strings` as t_79c04 WHERE t_79c04.`deleted_at` IS NULL AND t_79c04.`attribute_id` = 39 AND (t_79c04.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_3936d WHERE t_3936d.`deleted_at` IS NULL AND t_3936d.`attribute_id` = 47 AND (t_3936d.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_07d8d WHERE t_07d8d.`deleted_at` IS NULL AND t_07d8d.`attribute_id` = 49 AND (t_07d8d.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_numerics` as t_0506f WHERE t_0506f.`deleted_at` IS NULL AND t_0506f.`attribute_id` = 54 AND (t_0506f.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_numerics` as t_2172b WHERE t_2172b.`deleted_at` IS NULL AND t_2172b.`attribute_id` = 55 AND (t_2172b.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_dates` as t_b2d33 WHERE t_b2d33.`deleted_at` IS NULL AND t_b2d33.`attribute_id` = 174 AND (t_b2d33.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_numerics` as t_cc807 WHERE t_cc807.`deleted_at` IS NULL AND t_cc807.`attribute_id` = 175 AND (t_cc807.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_34123 WHERE t_34123.`deleted_at` IS NULL AND t_34123.`attribute_id` = 0 AND (t_34123.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_74163 WHERE t_74163.`deleted_at` IS NULL AND t_74163.`attribute_id` = 99 AND (t_74163.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_f3994 WHERE t_f3994.`deleted_at` IS NULL AND t_f3994.`attribute_id` = 107 AND (t_f3994.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_ee85d WHERE t_ee85d.`deleted_at` IS NULL AND t_ee85d.`attribute_id` = 108 AND (t_ee85d.`aid` = `properties`.`aid`))) AND (exists (SELECT 1 FROM `property_value_strings` as t_2f858 WHERE t_2f858.`deleted_at` IS NULL AND t_2f858.`attribute_id` = 109 AND (t_2f858.`aid` = `properties`.`aid`)))
问题现象与已尝试方案
上述查询概念上简单,但执行时会周期性导致MySQL进入“statistics”状态并无限期挂起。执行analyze table property_value_strings有时能让查询完成,但不确定是否适用于生产环境。查询正常执行时速度很快(本地10万条房产样本数据中,该查询返回7400条结果仅需43毫秒)。已尝试将optimizer_search_depth设置为0,但未解决问题。
疑问
- 是什么导致MySQL进入“statistics”状态挂起?我猜测是使用的EXISTS语句数量过多,但原因是什么?能否通过调整此类查询避免该问题?
- 这是搜索这些值表的最有效方式吗?我完全接受重新调整查询结构,寻求更优的搜索方案。
编辑补充
我尝试了多种解决方案,并标记了最有帮助的答案,但EAV模型存在局限性。在本地加载60万条房产数据后(远未达到生产数据集规模),原本在10万条数据上表现可接受的查询耗时超过10秒。我现在正放弃EAV模型,探索其他方案。
内容的提问来源于stack exchange,提问作者siannopollo
相关产品推荐
相关产品推荐

