针对特定查询的MySQL索引

时间:2012-08-09 09:57:43

标签: mysql sql optimization indexing query-optimization

我有这张桌子

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

现在我必须为每个事件生成某个部门的报告current attendance(即该成员是否已经办理登机手续),以及他们至少4周的出勤率(即attended / total一段时间的事件)

这适用于报告的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

这适用于报告的attended部分。 :

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

我可以简单地LEFT JOIN合并这两个查询 - 在第一个查询中再次使用attendance表。

最后是total部分

SELECT
  attendance_week,
  COUNT(DISTINCT event_id)
FROM
  attendance
WHERE
  attendance_week BETWEEN :start AND :end
GROUP BY
  attendance_week

这些是将为这些表运行的主要查询。此时,查询平均运行 150 - 200ms (根据phpMyAdmin),我觉得这很慢。 EXPLAIN告诉我,正在使用

所以这是我的问题:

  1. 有没有其他方法可以修改我的indeces和查询以使其更快?
  2. 我假设MySQL有一个编译语句的缓存。我不是在谈论结果缓存,认为PHP操作码与HTML缓存。我已经尝试了SQL_NO_CACHE,我仍然得到相同的响应时间,query_cache_size为0.我可以发誓我看到phpMyAdmin报告查询大约 800ms 一次(这是不可接受的但是我现在不懂。如何在每次运行时测量查询的真实速度?
  3. 如果我将这些查询放在存储过程中,这些会更快吗?
  4. 对存储方法有何想法?该数据库目前大小约为400MB。一年后,我不知道,也许3GB?这可扩展吗?在DBA方面,我真的很新,我已经阅读了主从复制和分区,但我不知道它是否对此有好处。
  5. 如果您需要更多信息,请在下方发表评论。我会尝试提供它。我确实尝试过这样做,但考虑到庞大的数据库(我迄今为止最大的数据库)和高性能的需求,我真的需要一些建议: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
    

    此查询在第一次运行时运行925ms,在后续请求运行15ms。

    这是上述查询的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
    

2 个答案:

答案 0 :(得分:2)

在表上添加covering或聚簇索引将为您提供最佳性能:

  1. 您还可以在表成员上添加额外的索引:

    成员indeces:(member_id,dept_id)

  2. 您可以启用Query Cache来缓存查询输出,但查询缓存不适用于过程。要衡量查询的准确速度,您可以使用mysqlslap client utility

  3. 存储过程中的查询在速度方面没有太大区别,但它会节省一些额外的查询解析开销并将输出发送给客户端。

  4. 使用分片或复制在不同服务器上分发数据将在可伸缩性方面提供帮助。对巨大的桌子进行分区也会对您有所帮助。

答案 1 :(得分:0)

  1. 您的设计似乎有效。我认为,报告应用程序在200毫秒(甚至高达800毫秒)内完成报告是完全正常的。至于新的索引,我首先会检查它是否真的值得做,'但是,如果你有所有成员平均只分布在5个depts上,那么member.dept_id上的索引将没有用 - 它更便宜在这种情况下执行全面扫描。

  2. 我没有看到测量查询“真实”速度的重点,因为数据库可以通过有效缓存数据来加速数据访问。因此,如果你处于一个新启动的数据库服务器上,你的查询需要800毫秒,进一步执行的时间会减少到50-100毫秒,那么这是一个很好的设置,这就是我在日常工作中的目标

  3. 我对此表示怀疑,因为与调用时间过程解析的所有语句的好处相比,存储过程将为您提供执行过程并获得结果所需的额外时间。

  4. 目前您的速度适用于非OLTP应用程序。对我而言,似乎通过attendance列对attendance_week表进行分区将为您提供良好的性能提升,因为您的所有查询都围绕此列进行。但是,如果系统中有更多数据,至少需要3-4周的数据,那么效益就会显现出来。

  5. 但是,对于OLTP系统,我的假设可能是错误的。您能指定所提供示例的有用使用区域吗?

    此外,您可以查看EXPLAIN语句的实际输出结果。