针对特定查询的MySQL索引优化

3

我有这个表格

attendance (4M rows at the moment, growing 1.2M per week):

-------------------------------------------------------------
| member_id | attendance_week | attendance_date | event_id  |
------------------------------------------------------------
|  INT (10) |   TINYINT(2)    |   TIMESTAMP     |TINYINT(3) |
-------------------------------------------------------------

attendance indeces:
--------------------------------------------------
| PRIMARY (attendance_week, member_id, event_id) |
| member_id (member_id)                          |
| event_id (event_id, attendance_week)
| total (attendance_week, event_id)              |
--------------------------------------------------

members (400k rows at the moment growing 750 a week):
-------------------------
| member_id |  dept_id  |
-------------------------
|  INT (10) |SMALLINT(5)|
-------------------------

member indeces:
-----------------------
| PRIMARY (member_id) |
| 
-----------------------

活动是每周进行的,这意味着您会看到每周的member_idevent_id成对出现。

现在我必须为某个部门生成一份报告,包括每个事件、当前出席情况(即此成员是否已签到)以及他们在至少4周内的出席情况(即持续时间内出席的 / 总活动数)。

这是报告中 current_attendance 部分的内容。我获取该部门的所有成员,并使用本周的事件进行LEFT JOIN,以便获取缺席的NULL

SELECT
  m.member_id AS id,
  a.event_id AS attended
FROM
  members AS m
LEFT JOIN
  attendance AS a
  ON
    a.member_id = m.member_id AND
    a.attendance_week = :week AND
    a.event_id = :event
WHERE
  m.dept_id = :dept
GROUP BY
  m.member_id

这是报告中的已出席部分。:

SELECT
  a.member_id,
  COUNT(a.event_id)
FROM
  attendance a 
  JOIN
    members m 
    ON 
      a.member_id = m.member_id AND
      m.dept_id = :dept
WHERE
  a.attendance_week BETWEEN :start AND :end
GROUP BY
  a.member_id

我可以通过在第一个查询中再次使用attendance表的LEFT JOIN来合并这两个查询。
最后是total部分。
SELECT
  attendance_week,
  COUNT(DISTINCT event_id)
FROM
  attendance
WHERE
  attendance_week BETWEEN :start AND :end
GROUP BY
  attendance_week

这些是将用于这些表的主要查询。目前,根据phpMyAdmin,这些查询平均运行时间为150-200ms,我认为这很慢。EXPLAIN告诉我我的索引正在使用
所以这里是我的问题:
  1. 有没有其他方法可以修改我的索引和查询使其更快?
  2. 我假设MySQL有一个编译语句的缓存。我不是在谈结果缓存,而是PHP opcode与HTML缓存之间的区别。我已经尝试了SQL_NO_CACHE,但仍然得到相同的响应时间,并且query_cache_size为0。我发誓曾经看到phpMyAdmin报告查询速度约为800ms(这是不可接受的),但现在我没有得到它们。如何测量每次运行查询的真实速度?
  3. 如果将这些查询放入存储过程中,它们会更快吗?
  4. 对于存储方法有什么想法?数据库当前的大小约为400MB。一年后,我不知道,也许是3GB?这是可扩展的吗?当涉及到DBA时,我真的很新,我已经阅读了主从复制和分区,但我不知道它是否适用于此。
如果您需要更多信息,请在下面发表评论。我真的尝试过独自完成这个任务,但考虑到巨大数据库(迄今为止我最大的数据库)和高性能的要求,我真的需要一些建议:D
谢谢
编辑
我刚刚意识到我的逻辑中有一个可怕的缺陷,新注册的成员将显示出低出勤率,因为第三个查询不考虑注册日期。我在我的成员表中有一个registration_date列,有没有办法将该变量合并到查询中?或者将所有三个查询合并为一个查询?因为它们都返回依赖于每个用户的值。
编辑
我已经成功合并了前两个查询:
    SELECT
      m.member_id AS id,
      a.event_id AS attended,
      COUNT(b.event_id) AS total_attended
    FROM
      members AS m
      LEFT JOIN
        attendance AS a
        ON
          a.member_id = m.member_id AND
          a.attendance_week = :week AND
          a.event_id = :event
      LEFT JOIN
        attendance AS b
        ON
          b.member_id = m.member_id AND
          b.attendance_week BETWEEN :start AND :end
    WHERE
      m.dept_id = :dept
    GROUP BY
      m.member_id

第一次运行此查询需要925毫秒,而随后的请求则只需要15毫秒。

以上查询的EXPLAIN结果如下:

members table:
id:            1
select_type:   SIMPLE
table:         m
type:          ref
possible_keys: dept_id
key:           dept_id
key_len:       3
ref:           const
rows:          88
Extra:         Using where; Using index

attendance table 1 (for the boolean attended part):
id:            1
select_type:   SIMPLE
table:         a
type:          eq_ref
possible_keys: PRIMARY,member_id,event_id,total
key:           PRIMARY
key_len:       6
ref:           const,arms_db.m.member_id,const
rows:          1
Extra:         Using index

attendance table 2 (for the total attendanded part):
id:            1
select_type:   SIMPLE
table:         b
type:          ref
possible_keys: PRIMARY,member_id,total
key:           member_id
key_len:       4
ref:           arms_db.m.member_id
rows:          5
Extra:         Using index

最后一个查询的EXPLAIN

id:            1
select_type:   SIMPLE
table:         attendance
type:          range
possible_keys: PRIMARY,toral
key:           total
key_len:       2
ref:           NULL
rows:          9
Extra:         Using where; Using index for groub-by

MySQL服务器版本是什么? - Déjà vu
5.5.25a 社区服务器 - Rolando Cruz
这些表是MyISAM还是InnoDB? - ypercubeᵀᴹ
第三个查询(总部分)似乎正在使用(event_id, attendance_week)索引,我认为这没有更好的索引了。它有多快? - ypercubeᵀᴹ
第一次运行需要200毫秒,后续运行只需要5毫秒。我的第二次编辑实际上更改了第三个查询的要求。这实际上是我之前在dba.stackexchange上尝试询问并由您编辑过的内容。http://dba.stackexchange.com/questions/22272/query-for-data-that-is-not-there - Rolando Cruz
2个回答

2
在表上添加覆盖索引或聚集索引将为您提供最佳性能:
  1. 您还可以在表成员上添加额外的索引:

    成员索引:(成员 ID,部门 ID)

  2. 您可以启用查询缓存来缓存查询输出,但查询缓存不适用于存储过程。要测量查询速度,请使用mysqlslap客户端实用程序

  3. 存储过程内的查询在速度方面不会有太大差异,但它会节省一些查询解析和将输出发送到客户端的额外开销。

  4. 使用分片或复制将数据分布在不同的服务器上,这将有助于扩展性。对于庞大的表进行分区也会使您受益。


对于问题 #4,这个设置适合分区和复制吗?两者都可以使用吗? - Rolando Cruz
将这些查询合并成一个是没有意义的,因为第三个查询有不同的WHERE条件和GROUP BY子句。但是您可以使用UNION ALL来合并所有三个查询的输出。关于registration_date的输出是正确的,但如果您想要操作它,那么可以通过某种方式进行操作。 - Omesh

0
  1. 你的设计看起来是有效的。我认为,在200毫秒内(甚至高达800毫秒)完成报告对于报告应用程序来说是完全可以接受的。至于新索引,我会先检查它是否真的值得做,因为,比如说,如果你的所有成员均匀分布在仅有的5个部门中,那么在member.dept_id上建立索引将没有用处——在这种情况下执行全表扫描更便宜。

  2. 我不明白测量查询的“真实”速度有什么意义,因为数据库的存在是为了通过有效地缓存数据来加快数据访问速度。因此,如果您处于这样一种情况:在刚启动的数据库服务器上,您的查询需要大约800毫秒,而进一步执行的时间降至50-100毫秒,则这是一个良好的设置,也是我日常工作的目标。

  3. 我怀疑这一点,因为存储过程将给您执行过程和获取结果所需的额外时间,与在调用过程时解析所有语句的好处相比,这些时间很短。

  4. 目前,您的速度对于非 OLTP 应用程序来说已经足够了。对我来说,似乎通过attendance_week列对attendance表进行分区将为您提供良好的性能提升,因为您所有的查询都围绕这个列展开。但是,只有当系统中有更多数据时,至少有3-4周的数据,才能看到好处。

我的假设可能对于 OLTP 系统来说是错误的。你能指定提供的示例的预期使用区域吗?

此外,看到查询语句的实际输出 EXPLAIN 语句会很有好处。


这实际上是一个OLTP系统(如果我正确理解维基百科所说的)。我在这里详细说明的是系统的报告部分,该部分是每个操作员在一系列交易之后生成的。以下是报告输入部分的描述:http://serverfault.com/questions/411804/system-requirements-of-a-write-heavy-applications-serving-hundreds-of-requests-p 我对请求进行了一些修改,现在考勤是批量发送的,但是member_id查找仍然是按每个成员的方式进行的。但我认为这仍将是一个写入密集型应用程序。 - Rolando Cruz
@RolandoCruz,查询在15毫秒内完成是很好的。你的EXPLAIN输出看起来非常不错。你还想要实现什么?我对这样的结果感到非常满意。 - vyegorov
当我看到那800毫秒时,我有点担心。所以我认为我可能做错了事情。我知道我可能只是过度优化,但考虑到这是我的第一个大型应用程序,我真的不知道哪些数字是好的,哪些是坏的:D 我会等待其他回答关于我的第二次编辑,并可能很快接受一个答案:D - Rolando Cruz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接