10

我有一个联系消息传递系统的查询,我执行的连接越多,该系统的速度就越慢。

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

该查询多次连接联系人字段表,并且对于我执行的每次连接,它需要两倍的时间。

这是查询。

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 ;

有谁知道它为什么运行这么慢?我该怎么做才能让它更快?我是否需要调整查询(我宁愿不调整结构)?我可以设置任何配置选项来加快速度吗?

同样奇怪的是,与我的 Debain 生产服务器相比,它似乎在我的 Windows 开发机器上运行得更快(几乎是即时的,与 30 多秒相比)。

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

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

因此,阅读 EAV 在 RDBMS 中表现不佳(或至少在我的情况下),是否可以增加一个配置选项以使其运行得更快(即我可以向其投入更多 RAM)?

4

4 回答 4

5

加快查询速度的一种方法是contact_data_fields 仅链接一次(on 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
于 2013-01-21T13:18:37.253 回答
3

不幸的是,您的查询有很多效率低下的地方。我认为您无法通过调整一些参数和添加更多 RAM 来解决问题:

  • 首先,我们不知道您的桌子的大小,以及您为什么需要转储整个桌子contact_data。没有额外的条件和限制(通常很重要)。
  • 我们也不知道,对于给定的,是否可以有多个具有相同 (contact_data_id, key) 的记录contact_data.id。我认为可以是 {0, 1} 记录,如果您有相应的唯一索引(最终需要作为索引以提高查询效率),这可以更加明确
  • SQL_CALC_FOUND_ROWS 是一个额外的杀手(如果你要使用 LIMIT),因为它使 MySQL 计算并扫描整个结果以计算行数(我只会用一个单独的查询来计算行数,获取裸 ID 并缓存它的结果. 如果表不经常更改,MySQL 自己的 Query Cache 可能就足够了)

只要在 上添加索引(contact_data_id, key),我就会将分组和排序隔离到一个子查询中,然后在 LEFT JOIN 上contact_data_fields(没有任何排序)。contact_data您当前的查询对, ,的乘积中的每一行进行相同的 LEFT JOIN 比较contact_data_tags,然后再对它们进行分组(更不用说您的服务器在对所有内容进行分组之前存储了整个中间结果并丢弃了重复数据)。contact_data_assignmentcontact_data_read

于 2013-01-21T13:06:38.800 回答
2

我将在所有这些有趣的评论中添加我自己在实体-属性-值-模型查询和 MySQL 方面的经验。

首先不要忘记您在 MySQL 中对连接数的下限是61 个连接。乍一看,这似乎是一个很大的数字。但是使用这个模型,它可以很容易地用一个不错的SQLSTATE[HY000]: General error: 1116.

我也经历了这些指数级的放缓。当我们第一次在 50 000 行表上进行 50 次连接的查询超过 20 秒时,我们发现在查询优化器中丢失了这些 15 秒上的 14.5 秒——似乎它试图猜测这 50 次连接的最佳连接顺序—— . 因此,只需在关键字STRAIGHT_JOIN之后添加关键字,SELECT我们就可以恢复正常时间。当然,这样做意味着您必须获得一个好的索引方案,并且您必须使用巧妙的连接顺序编写查询(具有最佳索引和最佳人口减少的表应该首先出现)。

SELECT STRAIGHT_JOIN (...)

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

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

我会添加“或者如果需要 95% 的查询时间来猜测这个订单”:-)

还可以直接在查询上查看此页面以获取其他查询优化器设置。

然后你有5.1 和 5.5之间的差异......这些版本之间有很多差异,就像使用两个不同的数据库服务器一样。您确实应该考虑在生产中使用 5.5,以提高速度(也请检查Percona)以及用于事务和锁的改进,如果您只需要一个原因,那就是您将在生产中遇到开发中没有的错误。

这些包含大量连接的查询将根据定义对服务器造成压力。您需要在 my.cnf 文件中进行一些微调来控制服务器行为。例如,尽量避免创建临时表(检查查询的解释输出)。一个 2 秒的查询可以变成一个 120 秒的查询,因为您达到了一个限制并转到临时文件来管理您的 20 或 30 个连接、排序和分组依据。与内存工作相比,将数据放入磁盘确实非常慢。这尤其受以下两个设置控制:

tmp_table_size = 1024M
max_heap_table_size = 1024M

在这里,我们说“如果需要少于 1Go 的 RAM,请保留内存工作以供请求”。当然,如果您这样做,请避免让 500 个并行脚本运行这些请求——如果您需要定期处理大量并行请求,请考虑避免使用这种数据方案。

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

于 2013-01-25T00:57:06.553 回答
1

基于 Mark Ba​​nnisters 查询,可能使用类似这样的方法将字段/值详细信息作为分隔列表返回:-

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 的中间行数),从子选择中获取联系人键/值详细信息可能会更快。

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
于 2013-01-23T12:05:14.527 回答