过滤按列分组的大型DB记录

时间:2015-07-21 08:00:11

标签: mysql sql database optimization aggregate-functions

TransactionHistory表:

    CREATE TABLE `TransactionHistory` (
      `id` varchar(200) NOT NULL,
      `transactionType` varchar(200) DEFAULT NULL,
      `startDate` bigint(20) DEFAULT NULL,
      `completionDate` bigint(20) DEFAULT NULL,
      `userId` varchar(200) DEFAULT NULL,
      `status` varchar(200) DEFAULT NULL,
      `error_code` varchar(200) DEFAULT NULL,
      `transactioNumber` varchar(200) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `transactioNumber_index` (`transactioNumber`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

用户表:

    CREATE TABLE `User` (
      `userId` varchar(200) NOT NULL,
      `name` varchar(200) DEFAULT NULL,
      PRIMARY KEY (`userId`),
      KEY `userId_index` (`userId`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

情景:

  • 通过transactioNumber对TransactionHistory进行分组
    • 如果groupSize == 1,
      • 在transactionType,startDate,completionDate,status,error_code
      • 中显示值
    • 如果groupSize> 1
      • 显示'' for transactionType
      • 显示MIN startdate,MAX startdate
      • 表示STATUS和ERROR_CODE
        • 显示状态= SUCCESS,error_code =' 0'如果组中的所有状态= SUCCESS,
        • display status = FAILED,error_code =' 99'如果group = FAILED中的所有状态,
        • 显示状态=警告,error_code =' -1'如果混合
    • 显示userName的名称(如果事务具有userId)

我提出了这个问题:

    SELECT tx.id, 
        CASE WHEN COUNT(*) = 1 THEN transactionType ELSE '' END as transactionType,
        CASE WHEN COUNT(*) = 1 THEN status ELSE ( 
            CASE WHEN COUNT(CASE WHEN STATUS = 'SUCCESS' THEN 1 END) = 0 THEN 'FAILED' 
            WHEN COUNT(CASE WHEN STATUS = 'FAILED' THEN 1 END) = 0 THEN 'SUCCESS' 
            ELSE 'WARNING' END) END as status,
        CASE WHEN COUNT(*) = 1 THEN error_code ELSE ( 
            CASE WHEN COUNT(CASE WHEN STATUS = 'SUCCESS' THEN 1 END) = 0 THEN '99' 
            WHEN COUNT(CASE WHEN STATUS = 'FAILED' THEN 1 END) = 0 THEN '0' 
            ELSE '-1' END) END as status
        MAX(completionDate) as completionDate, 
        MIN(startDate) as startDate,
        a.userId, a.name,
        transactioNumber
    FROM TransactionHistory tx LEFT JOIN User a ON tx.userId = a.userId 
    GROUP BY transactioNumber
    LIMIT 0, 20 //pagination

但是,如果我需要添加过滤,则查询需要很长时间才能完成。我读过在GROUP BY而不是HAVING之前放置WHERE过滤器会更快,但我不能正确过滤status和error_code,因为WARNING和-1值只出现在GROUP BY之后

    HAVING STATUS = 'WARNING'

此外,如果我需要计算分组条目的总数,则需要很长时间。

我的EXPLAIN显示以下内容

    select_type: SIMPLE
    table: tx
    type: ALL
    possible_keys: NULL
    key_len: NULL
    ref: NULL
    rows: 1140654
    Extra: Using temporary; Using filesort

    select_type: SIMPLE
    table: e
    type: eq_ref
    possible_keys: PRIMARY,id_index
    key_len: 202
    ref: db.tx.userId
    rows: 1
    Extra: Using where   

1 个答案:

答案 0 :(得分:1)

SUM(STATUS = 'SUCCESS')

可以缩短为

WHERE

这些必须按此顺序编写,并按以下顺序执行:GROUP BYHAVINGHAVING。您正确地发现,WHERE无法转换为COUNT(*)

  

此外,如果我需要计算分组条目的总数,则需要很长时间。

我不知道你的意思 - 你多次使用transactioNumber

idGROUP BY的关系是否为1:1?如果没有,则ORDER BY无效。

您没有LIMIT,因此(技术上),EXPLAIN SELECT ...定义不明确。

运行JOIN以查看优化程序如何执行查询。

这是一种可能有用的技术 - 推迟User。首先,从查询中删除所有提及SELECT的内容。然后,将SELECT z.id, z.transactionType, ... a.userId, a.name, z.transactioNumber FROM ( SELECT id, IF(COUNT(*) = 1, transactionType, '') as transactionType, ... FROM TransactionHistory GROUP BY transactioNumber ORDER BY transactioNumber LIMIT 0, 20 ) z LEFT JOIN User a ON z.userId = a.userId 子查询设为:

JOIN

这样,WHERE只会发生20次,而不会在TransactionHistory中每行发生一次。

修改

如果没有GROUP BY子句,优化器将查找有助于ORDER BY的索引。如果GROUP BYGROUP BY相同,那么它可以同时执行ORDER BYORDER BY。如果它们不同,则ORDER BY将成为单独的排序步骤。

具有混合方向的startdate DESC, transactionType ASC(例如startdate DESC, transactionType DESC)永远不能使用索引。它注定需要一个tmp表和排序。使用GROUP BY(两个DESC)可能会更好地工作,而不会过多地改变语义。

如果优化器无法使用的索引 ORDER BYLIMIT,那么它必须收集所有行并对其进行排序在应用INDEX之前。

对于1140654行,您希望尝试查询并让ORDER BY让优化程序在EXPLAIN中完成所有操作 - 这样它只需要查看20行,而不是1140654. My pagination blog进入其中一些。

GROUP BY可能会说“使用临时,使用filesort”。这可能适用于ORDER BY 和/或 GROUP BY。但是,这隐藏了需要两个排序的情况,一个用于ORDER BY,一个用于EXPLAIN FORMAT=JSONvar currentUser = PFUser.currentUser() var email = currentUser["email"] 确实在需要多种排序时明确说明。

仍然,“filesort”不是邪恶的。真正的性能杀手需要使用1140654行而不是20行。