首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >EAV结构化数据复杂搜索需要MySQL优化

EAV结构化数据复杂搜索需要MySQL优化
EN

Stack Overflow用户
提问于 2014-01-28 17:24:04
回答 1查看 788关注 0票数 0

我有一个包含EAV结构化数据的大型数据库,必须具有可搜索性和可分页性。我尝试了我书中的每一个技巧,以使它足够快,但在某些情况下,它仍然未能在合理的时间内完成。

这是我的桌子结构(只有相关的部分,如果你需要更多的话,可以问一下):

代码语言:javascript
复制
CREATE TABLE IF NOT EXISTS `object` (
  `object_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `oid` varchar(32) CHARACTER SET utf8 NOT NULL,
  `status` varchar(100) CHARACTER SET utf8 DEFAULT NULL,
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`object_id`),
  UNIQUE KEY `oid` (`oid`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `version` (
  `version_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `type_id` bigint(20) NOT NULL,
  `object_id` bigint(20) NOT NULL,
  `created` datetime NOT NULL,
  `status` varchar(100) CHARACTER SET utf8 DEFAULT NULL,
  PRIMARY KEY (`version_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `value` (
  `value_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `object_id` int(11) NOT NULL,
  `attribute_id` int(11) NOT NULL,
  `version_id` bigint(20) NOT NULL,
  `type_id` bigint(20) NOT NULL,
  `value` text NOT NULL,
  PRIMARY KEY (`value_id`),
  KEY `field_id` (`attribute_id`),
  KEY `action_id` (`version_id`),
  KEY `form_id` (`type_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

这是一个示例对象。我的数据库中大约有100万。每个对象可能有不同数量的属性,具有不同的attribute_id。

代码语言:javascript
复制
INSERT INTO `owner` (`owner_id`, `uid`, `status`, `created`, `updated`) VALUES (1, 'cwnzrdxs4dzxns47xs4tx', 'Green', NOW(), NOW());
INSERT INTO `object` (`object_id`, `type_id`, `owner_id`, `created`, `status`) VALUES (1, 1, 1, NOW(), NOW());
INSERT INTO `value` (`value_id`, `owner_id`, `attribute_id`, `object_id`, `type_id`, `value`) VALUES (1, 1, 1, 1, 1, 'Munich');
INSERT INTO `value` (`value_id`, `owner_id`, `attribute_id`, `object_id`, `type_id`, `value`) VALUES (2, 1, 2, 1, 1, 'Germany');
INSERT INTO `value` (`value_id`, `owner_id`, `attribute_id`, `object_id`, `type_id`, `value`) VALUES (3, 1, 3, 1, 1, '123');
INSERT INTO `value` (`value_id`, `owner_id`, `attribute_id`, `object_id`, `type_id`, `value`) VALUES (4, 1, 4, 1, 1, '2012-01-13');
INSERT INTO `value` (`value_id`, `owner_id`, `attribute_id`, `object_id`, `type_id`, `value`) VALUES (5, 1, 5, 1, 1, 'A cake!');

现在谈我目前的机制。我的第一次尝试是Mysql的典型方法。在我需要的任何东西上执行一个包含大量连接的大型SQL。完完全全的独裁者!由于内存耗尽,加载时间太长,甚至导致PHP和MySQL服务器崩溃。

因此,我将我的查询分成几个步骤:

1确定所有需要的attribute_ids.

我可以在引用对象的type_id的另一个表中查找它们。结果是一个attribute_ids列表。(此表与性能不太相关,因此不在我的示例中。)

:type_id包含来自我想要包含在搜索中的任何对象的所有type_ids。我已经在我的申请中得到了这个信息。所以这个很便宜。

代码语言:javascript
复制
SELECT * FROM attribute WHERE form_id IN (:type_id)

结果是一个type_id整数数组。

2搜索匹配对象编译了一个大型查询,它为我想要的每个条件添加一个内部联接。这听起来很可怕,但最后,它是最快的方法:

典型的生成查询可能如下所示。可悲的是,限制是必要的,否则我可能会得到太多的I,导致PHP在下一个查询中爆炸或中断IN语句:

代码语言:javascript
复制
SELECT DISTINCT `version`.object_id FROM `version`
INNER JOIN `version` AS condition1 
        ON `version`.version_id = condition1.version_id 
       AND condition1.created = '2012-03-04' -- Filter by version date
INNER JOIN `value` AS condition2 
        ON `version`.version_id = condition2.version_id
       AND condition2.type_id IN (:type_id) -- try to limit joins to object types we need
       AND condition2.attribute_id = :field_id2 -- searching for a value in a specific attribute
       AND condition2.value = 'Munich' -- searching for the value 'Munich'
INNER JOIN `value` AS condition3 
        ON `version`.version_id = condition3.version_id
       AND condition3.type_id IN (:type_id) -- try to limit joins to object types we need
       AND condition3.attribute_id = :field_id3 -- searching for a value in a specific attribute
       AND condition3.value = 'Green' -- searching for the value 'Green'
WHERE `version`.type_id IN (:type_id) ORDER BY `version`.version_id DESC LIMIT 10000

结果将包含来自我可能需要的任何对象的所有object_ids。我选择的是object_ids,而不是version_ids,因为我需要匹配对象的所有版本,而不管哪个版本匹配。

3排序和页面结果下一步,我将创建一个查询,该查询将按某个属性对对象进行排序,然后分页生成数组。

代码语言:javascript
复制
SELECT DISTINCT object_id
FROM value
WHERE object_id IN (:foundObjects)
AND attribute_id = :attribute_id_to_sort
AND value > ''
ORDER BY value ASC LIMIT :limit OFFSET :offset

结果是前一次搜索中的对象ids的排序和分页列表。

4在最后一步中获取完整的对象、版本和属性,我将为任何对象和以前的查询找到的版本选择所有值。

代码语言:javascript
复制
SELECT `value`.*, `object`.*, `version`.*, `type`.*
`object`.status AS `object.status`,
`object`.flag AS `object.flag`,
`version`.created AS `version.created`,
`version`.status AS `version.status`,
FROM version
INNER JOIN `type` ON `version`.form_id = `type`.type_id
INNER JOIN `object` ON `version`.object_id = `object`.object_id
LEFT JOIN value ON `version`.version_id = `value`.version_id
WHERE version.object_id IN (:sortedObjectIds) AND `version.type_id IN (:typeIds)
ORDER BY version.created DESC

然后通过PHP将结果编译成尼斯对象->version->值数组结构。

现在是问题

  • 这样的混乱还能以任何方式加速吗?
  • 我是否可以从我的搜索查询中删除限制10000的限制?

如果所有这些都失败了,也许可以切换数据库技术?见我的另一个问题:Database optimized for searching in large number of objects with different attributes

真实生活样本

表大小:对象- 193801行,版本- 193841行,值- 1053928行

代码语言:javascript
复制
SELECT * FROM attribute WHERE attribute_id IN (30)

SELECT DISTINCT `version`.object_id
FROM version  
INNER JOIN value AS condition_d4e328e33813 
     ON version.version_id = condition_d4e328e33813.version_id
    AND condition_d4e328e33813.type_id IN (30)
    AND condition_d4e328e33813.attribute_id IN (377) 
    AND condition_d4e328e33813.value LIKE '%e%'  
INNER JOIN value AS condition_2c870b0a429f 
     ON version.version_id = condition_2c870b0a429f.version_id
    AND condition_2c870b0a429f.type_id IN (30)
    AND condition_2c870b0a429f.attribute_id IN (376) 
    AND condition_2c870b0a429f.value LIKE '%s%' 
WHERE version.type_id IN (30) 
ORDER BY version.version_id DESC LIMIT 10000 -- limit to 10000 or it breaks!

解释:

代码语言:javascript
复制
id  select_type  table                   type      possible_keys                key         key_len ref                               rows      Extra   
1   SIMPLE       condition_2c870b0a429f  ref       field_id,action_id,form_id   field_id    4       const                             178639    Using where; Using temporary; Using filesort
1   SIMPLE       action                  eq_ref    PRIMARY                      PRIMARY     8       condition_2c870b0a429f.action_id  1         Using where
1   SIMPLE       condition_d4e328e33813  ref       field_id,action_id,form_id   action_id   8       action.action_id                  11        Using where; Distinct

完成对象搜索(峰值RAM: 5.91MB,时间: 4.64s)

代码语言:javascript
复制
SELECT DISTINCT object_id
FROM version
WHERE object_id IN (193793,193789, ... ,135326,135324) -- 10000 ids in here!
ORDER BY created ASC
LIMIT 50 OFFSET 0                                                  

完成对象排序(峰值RAM: 6.68MB,时间: 0.352s)

代码语言:javascript
复制
SELECT `value`.*, object.*, version.*, type.*,
    object.status AS `object.status`,
    object.flag AS `object.flag`,
    version.created AS `version.created`,
    version.status AS `version.status`,
    version.flag AS `version.flag`
FROM version
INNER JOIN type ON version.type_id = type.type_id
INNER JOIN object ON version.object_id = object.object_id
LEFT JOIN value ON version.version_id = `value`.version_id
WHERE version.object_id IN (135324,135326,...,135658,135661) AND version.type_id IN (30)
ORDER BY quality DESC, version.created DESC 

完成对象加载查询(峰值RAM: 6.68MB,时间: 0.083s)

将对象编译成已完成的数组(峰值RAM: 6.68MB,时间: 0.007s)

EN

回答 1

Stack Overflow用户

发布于 2014-01-28 17:35:46

只需在搜索查询之前添加一个解释:

代码语言:javascript
复制
EXPLAIN SELECT DISTINCT `version`.object_id FROM `version`, etc ...

然后检查“额外”列中的结果,它将为您提供一些加快查询速度的线索,例如在正确的字段中添加索引。

还有一些时候,您可以加入removeINNER,在您的Mysql中获得更多的结果,并通过使用PHP循环进行处理来过滤大数组。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21412479

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档