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

现在我必须为某个部门的每个事件生成一份报告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 - 200 毫秒(根据 phpMyAdmin),我认为这很慢。EXPLAIN告诉我,我的 indeces 正在被使用

所以这是我的问题:

  1. 有没有其他方法可以修改我的索引和查询以加快速度?
  2. 我假设 MySQL 有一个已编译语句的缓存。我不是在谈论结果缓存,想想 PHP 操作码与 HTML 缓存。我已经尝试过SQL_NO_CACHE,但我仍然得到相同的响应时间,并且query_cache_size是 0。我可以发誓我看到 phpMyAdmin 以大约800 毫秒的速度报告了一次查询(这是不可接受的),但我现在没有得到它们。我如何衡量每次运行查询的真实速度?
  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
4

2 回答 2

2

在表上添加覆盖或聚集索引将为您提供最佳性能:

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

    成员索引:(member_id,dept_id)

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

  3. 存储过程中的查询在速度方面不会产生太大影响,但它会节省一些额外的查询解析和向客户端发送输出的开销。

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

于 2012-08-09T10:32:11.160 回答
0
  1. 您的设计似乎有效。我认为,在 200 毫秒(甚至高达 800 毫秒)内完成报告对于报告应用程序来说是非常好的。至于新的索引,我会首先检查它是否真的值得做,因为,比如说,如果你所有的成员都平均分布在 5 个部门上,那么 index onmember.dept_id将没有用 - 执行完整扫描更便宜在这种情况下。

  2. 我认为衡量查询的“真实”速度没有意义,因为数据库可以通过有效地缓存数据来加速数据访问。因此,如果您在新启动的数据库服务器上遇到这样的情况,您的查询大约需要 800 毫秒,而进一步的执行时间会下降到 50-100 毫秒,那么这是一个很好的设置,这就是我在日常工作中的目标.

  3. 我对此表示怀疑,因为与调用时间过程解析所有语句的好处相比,存储过程会给你一点额外的时间来执行过程并获得它的结果。

  4. 目前,您的速度对于非 OLTP 应用程序来说还不错。对我来说,似乎attendance按列对表进行分区attendance_week会给您带来很好的性能提升,因为您的所有查询都围绕该列进行。但是,当您在系统中拥有更多数据(至少值得 3-4 周)时,好处就会显现出来。

不过,对于 OLTP 系统,我的假设可能是错误的。您能否指定所提供示例的预期使用区域?

此外,最好查看EXPLAIN查询语句的实际输出。

于 2012-08-09T10:42:25.167 回答