【问题标题】:MySQL - self join optimizationMySQL - 自连接优化
【发布时间】:2012-01-28 01:38:20
【问题描述】:

我有一个按 HomeId 的电话事件表。每行都有一个 EventId(挂机、摘机、响铃、DTMF 等)、TimeStamp、Sequence(自动递增)和 HomeId。我正在进行查询以查找特定类型的事件(即入站或出站呼叫)和持续时间。

我曾计划在此表上使用多重自联接来挑选通常表明一种或另一种发生类型的事件序列。 EG 入站呼叫将是一段时间不活动,然后没有 DTMF,然后响铃和呼叫者 ID(可能),然后摘机。我会找到下一个挂机并因此有持续时间。

我的表由 HomeId、EventId 和 Sequence 编制索引,并且有大约 6 万条记录。当我对我的查询进行“解释”时,它会显示索引和 75、75、1、1、748 的行数。似乎很可行。但是当我运行查询时,它需要超过 10 分钟(此时 MySQL 查询浏览器超时)。

查询外呼:

select pe0.HomeId, pe1.Stamp, pe1.mSec, timediff( pe4.Stamp, pe0.Stamp ) from Phone_Events pe0 
join Phone_Events pe1 on pe0.HomeId = pe1.HomeId and pe1.Sequence = pe0.Sequence - 1 and abs(timediff( pe0.Stamp, pe1.Stamp )) > 10
join Phone_Events pe2 on pe0.HomeId = pe2.HomeId and pe2.Sequence = pe0.Sequence + 1 and pe2.EventId = 22
join Phone_Events pe4 on pe4.HomeId = pe0.HomeId and pe4.EventId = 30 and pe4.Stamp > pe0.Stamp
where pe0.eventId = 12 and pe0.HomeId = 111
AND
    NOT EXISTS(SELECT * FROM Phone_Events pe3
               WHERE pe3.HomeId = pe0.HomeId
               AND pe3.EventId not in( 13, 22 ) 
               AND pe3.Stamp > pe0.Stamp and pe3.Stamp < pe4.Stamp );

有没有什么特定于自我加入的东西让这个速度变慢?有没有更好的方法来优化这个?杀手似乎是“不存在”部分 - 这部分是为了确保在最后一个“挂机”和当前“摘机”之间没有事件。

编辑:EventId 如下:

'1', 'device connection'  
'2', 'device disconnection'  
'3', 'device alarm'
'11', 'ring start'
'12', 'off hook'
'13', 'hang up(other end)'
'15', 'missed call'
'21', 'caller id'
'22', 'dtmf'
'24', 'device error'
'30', 'on hook'
'31', 'ring stop'

【问题讨论】:

  • 看起来 Stamp 上的索引会有所帮助
  • 主键是(Sequence, Home)。 (Sequence, Home), EventId 和 HomeId 上也有索引。
  • abs(timediff( pe0.Stamp, pe1.Stamp ))
  • 我的东西可以在这里杀死我们。每个 HomeId 在 Phone_Events 中有多少条记录?我们能看到相关表和列的架构,包括索引吗?

标签: mysql optimization join


【解决方案1】:

根据新信息完全重写。我的处理方法是从最内部的查询开始,以获取我们只基于 HomeID = 111 关心的所有记录,并确保它们返回时按序列 ID 预先排序(在 HomeID、序列上有索引)。众所周知,一个电话从拿起电话开始——eventID = 12,得到拨号音——eventid = 22,拨出,有人接听,直到电话重新挂机——eventid = 30) .如果是挂断(eventid=13),我们想忽略它。

我不知道您为什么要查看当前调用之前的序列#,不知道它是否真的有任何影响。看起来您只是想完成通话以及持续时间。也就是说,我将删除 LEFT JOIN Phone_Event 的部分和相应的 WHERE 子句。当您试图弄清楚这一点时,它可能就在那里。

无论如何,回到逻辑。最内层保证调用顺序是有序的。您不会同时接听两个电话。因此,首先让它们按顺序排列,然后我加入 SQLVars(它为查询创建内联变量 @NextCall)。这样做的目的是识别每次新呼叫即将开始(EventID = 12)。如果是这样,无论序列号是什么,并保存它。这在下一次调用之前将保持不变,因此所有其他“事件 ID”将具有相同的“起始序列 ID”。此外,我正在寻找其他事件...一个事件 = 22 基于起始序列 +1 并将其设置为标志。然后,基于通话开始的最大时间(仅在 eventid = 12 时设置)和通话结束(eventid = 30),最后是基于您检查挂断的标志(eventid = 13),即: 如果是挂断且没有连接,则不要考虑通话。

通过分组,我实质上是将每个呼叫汇总到自己的线路...按家庭 ID 和用于发起实际电话呼叫的序列号分组。一旦完成,我就可以查询数据并计算通话时长,因为开始/结束时间在同一行,不涉及 self-self-self 连接。

最后,where 子句...踢掉任何挂断的电话。同样,我不知道您是否还需要最后一个结束事件的起始呼叫时间的元素。

SELECT
      PreGroupedCalls.*,
      timediff( PreGroupedCalls.CallEndTime, PreGroupedCalls.CallStartTime ) CallDuration 
   from
      ( SELECT
              Calls.HomeID,
              @NextCall := @NextCall + if( Calls.EventID = 12, Calls.Sequence, @NextCall ) as NextNewCall,
              MAX( if( Calls.EventID = 12, Calls.Stamp, 0 )) as CallStartTime,
              MAX( if( Calls.EventID = 30, Calls.Stamp, 0 )) as CallEndTime,
              MAX( if( Calls.EventID = 22 and Calls.Sequence = @NewCallFirstSeq +1, 1, 0 )) as HadDTMFEntry,
              MAX( if( Calls.EventID = 13 and Calls.Sequence = @NewCallFirstSeq +1, 1, 0 )) as WasAHangUp
           from 
              ( select pe.HomeId, 
                       pe.Sequence,
                       pe.EventID,
                       pe.Stamp
                   from
                      Phone_Events pe
                   where 
                      pe.HomeID = 111
                   order by
                      pe.Sequence ) Calls,
              ( select @NextCall := 0 ) SQLVars
           group by
              Calls.HomeID,
              NextNewCall ) PreGroupedCalls

         LEFT JOIN Phone_Event PriorCallEvent
            ON PreGroupCalls.NextNewCall = PriorCallEvent.Sequence -1 
   where
         PreGroupedCalls.WasHangUp = 0
      AND ( PriorCallEvent.Sequence IS NULL
         OR abs(timediff( PriorCallEvent.Stamp, PreGroupedCalls.CallStartTime )) > 10 )

反馈意见/报告错误

要尝试修复 DOUBLE 错误,您显然需要在 SQLVars 选择中稍作更改。尝试以下操作

(选择@NextCall := CAST(0 as INT)) SQLVars

现在,IF() 在做什么......让我们来看看。

@NextCall + if(Calls.EventID = 12,Calls.Sequence, @NextCall)

表示查看事件 ID。如果是 12(即:摘机),则获取该条目的序列号。这将成为另一个呼叫的新“开始顺序”。如果没有,只需保留最后一个值集,因为它是正在进行的调用的延续。现在,让我们看一些模拟数据,以帮助更好地说明所有列

Original data                    Values that will ultimately be built into...
HomeID Sequence EventID Stamp    @NextCall
111    1        12      8:00:00    1  beginning of a new call
111    2        22      8:00:01    1  not a new "12" event, keep last value
111    3        30      8:05:00    1  call ended, phone back on hook
111    4        12      8:09:00    4  new call, use the sequence of THIS entry
111    5        22      8:09:01    4  same call
111    6        13      8:09:15    4  same call, but a hang up
111    7        30      8:09:16    4  same call, phone back on hook
111    8        12      8:15:30    8  new call, get sequence ID
111    9        22      8:15:31    8  same call...
111   10        30      8:37:15    8  same call ending...

Now, the query SHOULD create something like this
HomeID   NextNewCall   CallStartTime  CallEndTime   HadDTMFEntry  WasAHangUp
111      1             8:00:00        8:05:00       1             0
111      4             8:09:00        8:09:16       1             1
111      8             8:15:30        8:37:15       1             0

如您所见,@NextCall 将给定调用的所有顺序条目“分组”在一起,因此您不必只使用大于跨度信息或小于...它始终遵循特定路径的“事件”,所以无论是哪个启动呼叫的事件都是其余事件的基础,直到下一个呼叫开始,然后为该组呼叫获取该序列。

是的,它有很多要掌握..但希望现在对你来说更容易消化:)

【讨论】:

  • 嗯.. 我添加了新索引并运行了您的版本。仍然达到 10 分钟超时。有问题的服务器是一台 8cpu、16Gb RAM 的 RHEL 机器,上面没有其他负载(目前)。看起来这应该快得多。
  • @ethrbunny,让我问你这个......“HomeID”也是自动分配的吗?是呼叫发起的电话分机吗?时间戳范围...是否有您期望的时间范围限制?您可以在查询中发布一些示例数据吗?
  • @ethrbunny,您能否列出不同的事件 ID 及其对应的代码...我有一个可能可行的想法...此外,您有一个时间戳,但查询整个文件...是否考虑限制给定时间段...例如一天、一周、一个月?
  • 已编辑以包含 EventId 范围。 HomeId 是从安装电话传感器的位置分配的。尽管研究不到一年,但时间戳值没有限制。
  • NOT IN 子句在这里仍然是一个杀手。假设 EventID 上有索引,最好使用pe3.EventID &lt;&gt; 13 AND pe3.EventID &lt;&gt; 22
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
  • 1970-01-01
  • 2012-08-05
  • 2011-12-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多