2013-01-17 175 views
9

我有一个联系消息传递系统的查询正在越来越慢我的联接越来越慢。SQL查询按指数速度变慢

表结构基本上是一个联系人表和一个联系人字段表。

查询很多时候会联系联系人字段表,并且对于每次联接,查询需要两次。

这是查询。

SELECT SQL_CALC_FOUND_ROWS 
    `contact_data`.`id`, 
    `contact_data`.`name`, 
    `fields0`.`value` AS `fields0`, 
    `fields1`.`value` AS `fields1`, 
    `fields2`.`value` AS `fields2`, 
    ...etc... 
    CONTACT_DATA_TAGS(
     GROUP_CONCAT(DISTINCT `contact_data_tags`.`name`), 
     GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`), 
     GROUP_CONCAT(DISTINCT `contact_data_read`.`user`) 
    ) AS `tags`, 
    GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`) AS `assignments`, 
    `contact_data`.`updated`, 
    `contact_data`.`created` 
FROM 
    `contact_data` 
LEFT JOIN contact_data_tags ON contact_data.`id` = contact_data_tags.`data` 
LEFT JOIN contact_data_assignment ON contact_data.`id` = contact_data_assignment.`data` 
LEFT JOIN contact_data_read ON contact_data.`id` = contact_data_read.`data` 
LEFT JOIN contact_data_fields AS fields0 ON contact_data.`id` = fields0.`contact_data_id` AND fields0.`key` = :field1 
LEFT JOIN contact_data_fields AS fields1 ON contact_data.`id` = fields1.`contact_data_id` AND fields1.`key` = :field2 
LEFT JOIN contact_data_fields AS fields2 ON contact_data.`id` = fields2.`contact_data_id` AND fields2.`key` = :field3 
...etc... 
GROUP BY contact_data.`id` 
ORDER BY `id` DESC 

这是表结构:

CREATE TABLE IF NOT EXISTS `contact_data` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(200) NOT NULL, 
    `format` varchar(50) NOT NULL, 
    `fields` longtext NOT NULL, 
    `url` varchar(2000) NOT NULL, 
    `referer` varchar(2000) DEFAULT NULL, 
    `ip` varchar(40) NOT NULL, 
    `agent` varchar(1000) DEFAULT NULL, 
    `created` datetime NOT NULL, 
    `updated` datetime NOT NULL, 
    `updater` int(10) unsigned DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `name` (`name`), 
    KEY `url` (`url`(333)), 
    KEY `ip` (`ip`), 
    KEY `created` (`created`), 
    KEY `updated` (`updated`), 
    KEY `updater` (`updater`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE IF NOT EXISTS `contact_data_assignment` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `user` int(10) unsigned NOT NULL, 
    `data` int(10) unsigned NOT NULL, 
    `created` datetime NOT NULL, 
    `updater` int(10) unsigned DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `unique_assignment` (`user`,`data`), 
    KEY `user` (`user`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE IF NOT EXISTS `contact_data_fields` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `contact_data_id` int(10) unsigned NOT NULL, 
    `key` varchar(200) NOT NULL, 
    `value` text NOT NULL, 
    `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 
    PRIMARY KEY (`id`), 
    KEY `contact_data_id` (`contact_data_id`), 
    KEY `key` (`key`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE IF NOT EXISTS `contact_data_read` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `user` int(10) unsigned NOT NULL, 
    `data` int(10) unsigned NOT NULL, 
    `type` enum('admin','email') NOT NULL, 
    `created` datetime NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `user` (`user`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE IF NOT EXISTS `contact_data_tags` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(200) NOT NULL, 
    `data` int(10) unsigned NOT NULL, 
    `created` datetime NOT NULL, 
    `updater` int(10) unsigned DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `unique_tag` (`name`,`data`), 
    KEY `name` (`name`), 
    KEY `data` (`data`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

DELIMITER $$ 
CREATE FUNCTION `contact_data_tags`(`tags` TEXT, `assigned` BOOL, `read` BOOL) RETURNS text CHARSET latin1 
BEGIN 
    RETURN CONCAT(
     ',', 
     IFNULL(`tags`, ''), 
     ',', 
     IF(`tags` IS NULL OR FIND_IN_SET('Closed', `tags`) = 0, 'Open', ''), 
     ',', 
     IF(`assigned` IS NULL, 'Unassigned', ''), 
     ',', 
     IF(`read` IS NULL, 'New', ''), 
     ',' 
    ); 
END$$ 

DELIMITER ; 

任何人都知道为什么,它运行这么慢?我能做些什么来使其更快?我是否需要调整查询(我不想调整结构)?有没有我可以设置的配置选项来加速它?另外奇怪的是,它似乎在我的Windows开发机器上运行得更快,与我的Debain生产服务器相比(几乎是瞬间的,相比于30多秒)。

但Windows机器远不如Debain服务器(8核Xeon,32GB RAM)强大。

在Debian上运行MySQL 5.1.49(我无法更新)和Windows上的5.5.28。

因此,读取EAV在RDBMS(或者至少在我的情况下)中表现不佳的情况下,我可以增加一个配置选项以使其运行速度更快(即,我可以只用更多的RAM)?

+4

啊,这是实体 - 属性 - 价值 - 模型的喜悦。在关系数据库中工作得不好。 http://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model RDBMS喜欢事先知道它的字段。您是否需要将所有这些可配置的contact_data_fields公开给数据库(用于查询)?如果没有,可能会将JSON存储在CLOB中。或者使用NoSQL文档数据库。或者如果它们不可配置,只需使用“常规”列即可。 – Thilo

+0

@Thilo是的,我需要暴露他们的查询。出于这个原因,我离开了JSON。 – Petah

+1

更正:EAV在某些数据库中运行得很好,具有正确的密钥和/或聚类表结构。 – wildplasser

回答

5

一来加快查询的办法是链接到contact_data_fields只有一次(上contact_data.id = contact_data_fields.contact_data_id),并改变领域列是max表情 - 像这样:

SELECT SQL_CALC_FOUND_ROWS 
    `contact_data`.`id`, 
    `contact_data`.`name`, 
    MAX(CASE WHEN fields.`key` = :field1 THEN fields.`value` END) AS `fields0`, 
    MAX(CASE WHEN fields.`key` = :field2 THEN fields.`value` END) AS `fields1`, 
    MAX(CASE WHEN fields.`key` = :field3 THEN fields.`value` END) AS `fields2`, 
    ...etc... 
    CONTACT_DATA_TAGS(
     GROUP_CONCAT(DISTINCT `contact_data_tags`.`name`), 
     GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`), 
     GROUP_CONCAT(DISTINCT `contact_data_read`.`user`) 
    ) AS `tags`, 
    GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`) AS `assignments`, 
    `contact_data`.`updated`, 
    `contact_data`.`created` 
FROM 
    `contact_data` 
LEFT JOIN contact_data_tags ON contact_data.`id` = contact_data_tags.`data` 
LEFT JOIN contact_data_assignment ON contact_data.`id` = contact_data_assignment.`data` 
LEFT JOIN contact_data_read ON contact_data.`id` = contact_data_read.`data` 
LEFT JOIN contact_data_fields AS fields 
     ON contact_data.`id` = fields.`contact_data_id` 
...etc... 
GROUP BY contact_data.`id` 
ORDER BY `id` DESC 
+0

+1,我认为这是一个额外的奖励,以防他的'contact_data_fields'表被实际抛弃,但我怀疑它=) – newtover

+0

我还没有尝试过,但很快就会做,让你知道。 – Petah

+0

这工作很好,谢谢。我会添加另一个赏金来奖励你。 – Petah

3

不幸的是,你的查询有很多低效率。我不认为你会管理的只是调整一些参数和添加更多的内存来解决这个问题:

  • 首先,我们不知道你的表的大小,以及为什么你需要放弃整个表contact_data。没有附加条件和限制(通常很重要)。
  • 我们不知道,如果可以有多个记录(给定contact_data.id相同(contact_data_id,键))。我认为thre可以是{0,1}记录,并且如果您需要相应的唯一索引(最终将索引用作查询的有效索引),可以使其更加明确
  • SQL_CALC_FOUND_ROWS是额外的(如果你打算使用LIMIT),因为它使得MySQL计算并扫描整个结果来计算行数(我只是用一个单独的查询来计算行数来获取裸ID并缓存它的结果。如果你对(contact_data_id, key)添加索引的表不会改变非常频繁)

一旦缓存可能就足够了,我会隔离分组和排序为子查询,然后留在contact_data_fields JOIN(不加任何分拣)。当前查询对contact_data,contact_data_tags,contact_data_assignment,contact_data_read的产品中的每一行进行相同的LEFT JOIN比较,然后对它们进行分组(不提及您的服务器存储整个中间结果在分组之前进行分组并重复数据丢弃) 。

+0

数据大小约为'contact_data'中的20,000行,'contact_data_fields'中为200,000。其他连接并不重要,如果需要可能会被删除。没有额外的地方陈述,有时会没有限制(导出到xls)。是的,一个'contact_data_id'可以有多个相同的密钥。 'SQL_CALC_FOUND_ROWS'是必需的,因为我需要统计数字“显示100,000行中的100个被过滤的行数为100000” – Petah

1

基于马克扶手查询,可能使用这样的返回字段/值细节分隔列表: -

SELECT SQL_CALC_FOUND_ROWS 
    `contact_data`.`id`, 
    `contact_data`.`name`, 
    GROUP_CONCAT(CONCAT_WS(',', contact_data_fields.`key`, contact_data_fields.`value`)), 
    CONTACT_DATA_TAGS(
     GROUP_CONCAT(DISTINCT `contact_data_tags`.`name`), 
     GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`), 
     GROUP_CONCAT(DISTINCT `contact_data_read`.`user`) 
    ) AS `tags`, 
    GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`) AS `assignments`, 
    `contact_data`.`updated`, 
    `contact_data`.`created` 
FROM 
    `contact_data` 
LEFT JOIN contact_data_tags ON contact_data.`id` = contact_data_tags.`data` 
LEFT JOIN contact_data_assignment ON contact_data.`id` = contact_data_assignment.`data` 
LEFT JOIN contact_data_read ON contact_data.`id` = contact_data_read.`data` 
LEFT JOIN contact_data_fields ON contact_data.`id` = contact_data_fields.`contact_data_id` 
WHERE contact_data_fields.`key` IN (:field1, :field2, :field3, etc) 
GROUP BY contact_data.`id` 
ORDER BY `id` DESC 

根据麻木匹配在contact_data_tags,contact_data_assignment和contact_data_read台(和因此可能的中间行的每个contact_data.id数)的行ER那么它可以更快获得从一个子选择接触的键/值的信息。

SELECT SQL_CALC_FOUND_ROWS 
    `contact_data`.`id`, 
    `contact_data`.`name`, 
    Sub1.ContactKeyValue, 
    CONTACT_DATA_TAGS(
     GROUP_CONCAT(DISTINCT `contact_data_tags`.`name`), 
     GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`), 
     GROUP_CONCAT(DISTINCT `contact_data_read`.`user`) 
    ) AS `tags`, 
    GROUP_CONCAT(DISTINCT `contact_data_assignment`.`user`) AS `assignments`, 
    `contact_data`.`updated`, 
    `contact_data`.`created` 
FROM 
    `contact_data` 
LEFT JOIN contact_data_tags ON contact_data.id = contact_data_tags.`data` 
LEFT JOIN contact_data_assignment ON contact_data.id = contact_data_assignment.`data` 
LEFT JOIN contact_data_read ON contact_data.id = contact_data_read.`data` 
LEFT JOIN (SELECT contact_data_id, GROUP_CONCAT(CONCAT_WS(',', contact_data_fields.`key`, contact_data_fields.`value`)) AS ContactKeyValue FROM contact_data_fields 
WHERE fields.`key` IN (:field1, :field2, :field3, etc) GROUP BY contact_data_id) Sub1 ON contact_data.id = Sub1.contact_data_id 
GROUP BY contact_data.id 
ORDER BY `id` DESC 
+0

连接值的问题是无法搜索和排序。或者我错过了什么? – Petah

+0

不理想的搜索它们,并可能慢到足以使用任何性能节省,但取决于你想如何搜索它们。在阅读这个SQL的代码中,它可能会很容易,但不能真正评论这里发布的信息。在SQL中,这很难,但如果需要的话,你可以在集合中使用find。你也可以不用连接值,并且每个field1,field2等都有一行,然后在代码中处理它。 – Kickstart

2

我将添加到所有这些兴趣评论我自己的经验与实体属性值模型查询和MySQL。

首先别忘了你有MySQL中的下限上加入61 joins的数量。起初它似乎是一个很大的数字。但有了这个模型,它可以很容易地崩溃你的查询,好SQLSTATE[HY000]: General error: 1116

我经历了这些指数的放缓,以及。当我们第一次达到超过了50 20多岁的查询在50个000行的表连接,我们发现,14.5s在论文15S失去了在查询优化器 - 看来这是试图想最好的连接顺序论文50加入 - 。因此,只需在关键字SELECT后面添加关键字STRAIGHT_JOIN,我们就可以恢复正常。当然,这意味着你必须得到一个很好的索引方案,并且你必须用聪明的连接顺序编写你的查询(首先出现具有最佳索引和最佳总体缩减的表格)。

SELECT STRAIGHT_JOIN (...) 

请注意,该关键字也可用于JOIN语法。

STRAIGHT_JOIN强制优化器按它们在FROM子句中列出的顺序连接表。如果优化器以非最优顺序加入表,可以使用它来加速查询。

我要补充“或者如果它需要的TE查询时间95%至猜测这令” :-)

检查也this page可以直接在查询其他查询优化设置。

然后你有5.1和5.5之间的差异......好吧,这两个版本之间有太多的差别,就像在与两个不同的数据库服务器一起工作。你真的应该考虑在生产中使用5.5,为速度的改进措施(还要检查Percona),而且还为交易和锁的改进措施,如果你只需要一个理由是,你会得到在生产中,你去不是在开发中有错误。

这些包含大量连接的查询将根据定义强调服务器。你会需要一些微调在my.cnf文件来控制服务器的行为。例如,尽量避免创建临时表(检查查询中的解释输出)。 2s查询可能会成为120s查询,只是因为您达到了限制并转到临时文件来管理您的20或30个连接并对其进行排序和分组。与内存工作相比,将数据放在磁盘上真的很慢。

tmp_table_size = 1024M 
max_heap_table_size = 1024M 

这里我们说的“保持在请求存储工作,如果它比RAM 1Go少取”:这是特别是论文的两个设置控制。当然,如果你这样做,避免有500个并行脚本运行这些请求 - 如果你需要定期处理大量的并行请求,可以考虑避免这种数据方案。

这也引出了一个重要的观点。您正在达到一个请求复杂性的前沿。 SQL服务器通常比您的应用程序更快地聚合一个结果中的数据。但是,当数据的大小很大,并且在查询中添加了很多索引(每个连接至少有一个索引),然后对结果进行排序,分组,甚至与group_contact进行汇总...... MySQL肯定会使用临时文件它会很慢。通过使用几个简短的查询(一个没有分组的主要查询,然后是10或200个查询来获取您对group_contact字段的内容)例如,通过避免临时文件使用可能会更快。

+0

感谢您的信息。关于升级产品,我注意到我的5.5安装比5.1更严格,因此破坏了我的一些代码。 – Petah

+0

^Petah。此外,我忘了添加一个点,检查只有数字用于索引,文本列索引执行非常糟糕,并使用mor空间。 – regilero