【问题标题】:MysqL big table query optimizationMysql大表查询优化
【发布时间】:2016-04-14 10:43:18
【问题描述】:

我有一个聊天应用程序。我有一个 api,它返回用户交谈的用户列表。但是当达到100000行数据时,mysql返回列表消息需要很长时间。 这是我的消息表

CREATE TABLE IF NOT EXISTS `messages` (
  `_id` int(11) NOT NULL AUTO_INCREMENT,
  `fromid` int(11) NOT NULL,
  `toid` int(11) NOT NULL,
  `message` text NOT NULL,
  `attachments` text NOT NULL,
  `status` tinyint(1) NOT NULL DEFAULT '0',
  `date` datetime NOT NULL,
  `delete` varchar(50) NOT NULL,
  `uuid_read` varchar(250) NOT NULL,
  PRIMARY KEY (`_id`),
  KEY `fromid` (`fromid`,`toid`,`status`,`delete`,`uuid_read`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=118561 ;

这是我的用户表(简化)

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `login` varchar(50) DEFAULT '',
  `sex` tinyint(1) DEFAULT '0',
  `status` varchar(255) DEFAULT '',
  `avatar` varchar(30) DEFAULT '0',
  `last_active` datetime DEFAULT NULL,
  `active` tinyint(1) DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=15523 ;

这是我的查询(对于 id 为 1930 的用户)

select SQL_CALC_FOUND_ROWS `u_id`, `id`, `login`, `sex`, `birthdate`, `avatar`, `online_status`, SUM(`count`) as `count`, SUM(`nr_count`) as `nr_count`, `date`, `last_mesg` from
(
(select `m`.`fromid` as `u_id`, `u`.`id`, `u`.`login`, `u`.`sex`, `u`.`birthdate`, `u`.`avatar`, `u`.`last_active` as online_status, COUNT(`m`.`_id`) as `count`, (COUNT(`m`.`_id`)-SUM(`m`.`status`)) as `nr_count`, `tm`.`date` as `date`, `tm`.`message` as `last_mesg` from `messages` as m inner join `messages` as tm on `tm`.`_id`=(select MAX(`_id`) from `messages` as `tmz` where `tmz`.`fromid`=`m`.`fromid`) left join `users` as u on `u`.`id`=`m`.`fromid` where `m`.`toid`=1930 and `m`.`delete` not like '%1930;%' group by `u`.`id`)
UNION
(select `m`.toid as `u_id`, `u`.`id`, `u`.`login`, `u`.`sex`, `u`.`birthdate`, `u`.`avatar`, `u`.`last_active` as online_status, COUNT(`m`.`_id`) as `count`, 0 as `nr_count`, `tm`.`date` as `date`, `tm`.`message` as `last_mesg` from `messages` as m inner join `messages` as tm on `tm`.`_id`=(select MAX(`_id`) from `messages` as `tmz` where `tmz`.`toid`=`m`.`toid`) left join `users` as u on `u`.`id`=`m`.`toid` where `m`.`fromid`=1930 and `m`.`delete` not like '%1930;%' group by `u`.`id`)
order by `date` desc ) as `f` group by `u_id` order by `date` desc limit 0,10

请帮助优化此查询

我需要什么, 用户与谁交谈(姓名、性别等) 最后一条消息是什么(来自我或给我) 消息数(全部) 未读消息计数(仅限我)

查询运行良好,但耗时太长。

输出一定是这样的

【问题讨论】:

  • 您没有提供EXPLAIN 的输出。每个与 MySQL 相关的问题都有。从我所看到的 - 你正在做一个 LIKE 在开头和结尾使用通配符的查询 - 这表示全表扫描(因此它会遍历表的整个数据)。没有提到配置,所以我们不知道 MySQL 是否可以正确利用您的硬件。从问题来看,您正在运行默认配置,在机械驱动器上,没有任何优化,如果您必须执行 LIKE 搜索以获取 id = 1930 的用户的数据,那么您查询它完全错误我害怕。
  • 我用输出图片编辑了问题。当用户删除消息时,我正在添加“user_id+分号”来删除列。所以在查询中有“m.delete not like '%1930;%'”。所以删除的消息不会被检索。
  • 您使用 LIKE 运算符引用的删除列的目的是什么?如果那是删除消息的用户的 ID,它应该是一个 INT单独的连接表来保存这些 ID。
  • 如果您删除该条件,查询也需要很长时间。但我会用单独的表格测试已删除的消息。您还有其他建议吗?

标签: mysql large-data


【解决方案1】:

您的查询和数据库存在一些设计问题。

  • 您应该避免使用关键字作为列名,例如 delete 列或 count 列;
  • 您应避免选择未在 group by 中声明且没有聚合函数的列...虽然 MySQL 允许这样做,但这不是标准,您无法控制将选择哪些数据;
  • not like 构造可能会导致查询出现不良行为,因为 '%1930;%' 可能匹配 11930; 并且 11930 不等于 1930;
  • 应避免以% 通配符开头和结尾的like 构造,这将导致文本处理时间更长;
  • 您应该设计一种更好的方式来表示消息删除,可能是更好的标志和/或另一个表来保存与操作相关的任何重要数据;
  • 尝试在连接条件之前limit您的结果(使用派生表)执行较少的处理;

我试图以我理解的最佳方式重写您的查询。我已经在一个约 200.000 行且没有索引的消息表中执行了我的查询,它在 0.15 秒内执行。但是,当数据量增加时,您当然应该创建正确的索引以帮助它更好地执行。

SELECT SQL_CALC_FOUND_ROWS 
  u.id, 
  u.login, 
  u.sex, 
  u.birthdate, 
  u.avatar, 
  u.last_active AS online_status, 
  g._count, 
  CASE WHEN m.toid = 1930 
    THEN g.nr_count 
    ELSE 0 
  END AS nr_count, 
  m.`date`, 
  m.message AS last_mesg 
FROM
(

  SELECT 
    MAX(_id) AS _id, 
    COUNT(*) AS _count, 
    COUNT(*) - SUM(m.status) AS nr_count
  FROM messages m
  WHERE 1=1
    AND m.`delete` NOT LIKE '%1930;%' 
    AND
    (0=1
      OR m.fromid = 1930 
      OR m.toid   = 1930
    )
  GROUP BY 
    CASE WHEN m.fromid = 1930 
      THEN m.toid 
      ELSE m.fromid 
    END
  ORDER BY MAX(`date`) DESC
  LIMIT 0, 10
) g
INNER JOIN messages AS m ON 1=1 
  AND m._id = g._id
LEFT JOIN users AS u ON 0=1 
  OR (m.fromid <> 1930 AND u.id = m.fromid)
  OR (m.toid   <> 1930 AND u.id = m.toid)
ORDER BY m.`date` DESC
;

【讨论】:

  • LIMIT 对加快查询没有任何作用。这让他们变得更慢。尽管您的回答很有帮助,但我认为应该删除关于 LIMIT 的要点。
  • @N.B.如果您确实限制了将要连接的数据量,不会产生任何改进?
  • 因为LIMIT 的工作原理 - 不,不是真的。如果查询必须非常复杂,那几乎总是意味着有更好的方法。
  • @N.B.我认为您应该描述LIMIT 是如何工作的,以解释究竟是什么问题......我在manual 中读到的是相反的:If you combine LIMIT row_count with ORDER BY, MySQL ends the sorting as soon as it has found the first row_count rows of the sorted result, rather than sorting the entire result. If ordering is done by using an index, this is very fast.
  • 没问题 - 查询时必须考虑两件事。首先是LIKE %% 总是扫描整个表。第二个是这个 - LIMIT X, Y 这样做:它根据条件获取它找到的整个记录​​集,然后获取 X 记录并丢弃 Y 记录。没有LIMIT,你就不会处理丢弃问题——你做的越多,它就越慢。 OP 的查询无法真正优化,因为他没有正确构建表,所以他最终处理了 LIKE - 这是问题的核心。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多