【问题标题】:High cpu usage by mysql querymysql查询的cpu使用率高
【发布时间】:2013-12-15 20:59:54
【问题描述】:

我在使用这个查询时遇到了一些问题,每次我使用它时,cpu 使用率都会从 5% 变为 67%-100%。

我正在通过 java 服务在 ubuntu 中运行 mysql 服务器,但即使我通过任何 mysql ide 执行查询,结果都是一样的。

我已经在网上进行了一些搜索,所以我发布了 mysql 的配置文件。我添加了一些属性,然后我在一些帖子中找到了,但我认为我只是让它变得更糟。

嗯,这是我的 my.cnf 文件:

[mysqld]
innodb_file_per_table=1
innodb_buffer_pool_size = 256M
wait_timeout = 1800
local-infile=0
open_files_limit=10192
query_cache_size=128M
join_buffer_size=128K
thread_cache_size=4
table_cache=64
key_buffer_size=128M
user        = mysql
pid-file    = /var/run/mysqld/mysqld.pid
socket      = /var/run/mysqld/mysqld.sock
port        = 1336
basedir     = /usr
datadir     = /var/lib/mysql
tmpdir      = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
bind-address        = 0.0.0.0

key_buffer      = 2014M
max_allowed_packet  = 2014M
thread_stack        = 512K
thread_cache_size       = 1024
myisam-recover         = BACKUP
max_connections        = 200

query_cache_limit   = 2048M

log_error = /var/log/mysql/error.log

expire_logs_days    = 10
max_binlog_size         = 100M

[mysqldump]
quick
quote-names
max_allowed_packet  = 16M

[mysql]

[isamchk]
key_buffer      = 16M

!includedir /etc/mysql/conf.d/

我正在使用这个查询:

select regPosition.deviceId, count(regPosition.speed), max(regPosition.speed) from regPosition where (TIMESTAMPDIFF(MINUTE, lastPositionTime,now()) <= '5') and regPosition.speed >= '10' group by regPosition.deviceId;

该表的类型是 Myisam,它有大约 2M 的注册表,并以 idPosition 作为索引。 这是创建表查询:

    CREATE TABLE `regPosition` (
  `idPosition` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Id autoincremental.',
  `deviceId` int(5) NOT NULL COMMENT 'Id numérico del equipo. Identificador único para cada vehículo.',
  `lastPositionTime` datetime NOT NULL COMMENT 'Fecha/hora en que se registra la marca de posición (realizada por el dvr).',
  `divisionew` varchar(2) DEFAULT NULL COMMENT 'Orientación  Este u Oeste.',
  `longitude` int(11) NOT NULL COMMENT 'longitud.',
  `divisionns` varchar(2) DEFAULT NULL COMMENT 'Orientación Norte o Sur.',
  `latitude` int(11) NOT NULL COMMENT 'Latitud.',
  `direction` int(11) DEFAULT NULL COMMENT 'Dirección en que apunta el dispositivo.',
  `gradeLon` varchar(100) DEFAULT NULL COMMENT 'Longitud transformada a grados (en decimal).',
  `gradeLat` varchar(100) DEFAULT NULL COMMENT 'Latitud transformada a grados (en decimal).',
  `speed` int(11) NOT NULL COMMENT 'Velocidad del vehículo. Registrada por el dvr',
  PRIMARY KEY (`idPosition`),
  KEY `index` (`idPosition`) USING HASH
) ENGINE=MyISAM AUTO_INCREMENT=6562682 DEFAULT CHARSET=latin1;

[编辑]

查询的目的是获取设备的id和比速度大于10的次数(这只是一个例子,可能更多)并得到数据库记录的最大速度。

这个想法是这样的:如果在 5 分钟内速度超过 60kmh 有 5 次,我需要知道设备的 ID、最大速度和超过速度限制的次数。

如果你能给我任何帮助,我会很高兴:)。

感谢您的帮助。

【问题讨论】:

  • 发布查询说明
  • 对计算值执行具有WHERE 的查询几乎可以保证会导致表扫描,如果您有大量数据,这将是一个坏消息。
  • 那么,在这种情况下我该怎么办?我无法修改表结构,我需要每 5 分钟或更短时间检查一次,以发出警报,告知速度限制突破。
  • @JuanEnriqueRiquelme 你说你不能修改表结构。但是你能在表中添加另一个索引吗? lastPositionTime 上的索引应该使您的 WHERE 子句更快。此外,如果您在另一个程序中执行此操作,那么您可以跟踪每次迭代中表中存在的最大 idPosition,然后将 AND idPosition &gt; {previousMaxIdPosition} 添加到您的查询中。
  • 不错的一个@dg99 我要试试那个,我会告诉你怎么样。

标签: mysql ubuntu cpu-usage myisam


【解决方案1】:

其实我发现了cpu使用率高的问题。

问题是表的索引不好,基本上是我创建索引时定义的顺序。

当我在查询中使用“解释”时,我发现搜索是在数百万个注册表中完成的,即使使用直接过滤器(即使它发生在限制 100 时)。

原因是索引,所以我重新调整了索引的顺序,这样解释显示搜索是在不到 400 个注册表中完成的,并且 cpu 使用率非常好。

例如这个sql:

EXPLAIN
SELECT d.deviceId, r.divisionew 
FROM 
device d, regPosition r 
WHERE 
d.enabled = 1 
AND d.deviceId = r.deviceId 
AND (DATE_SUB(now(),INTERVAL 8 MINUTE)) < r.lastPositionTime 
ORDER BY d.deviceId DESC

这个人在不使用索引的情况下进行搜索,因此查询完成了对大约 800 万个注册表的搜索。

重新分配索引并且顺序正确,此查询在不到 400 行中完成搜索。

所以基本上改变索引的顺序我可以让索引正常工作,这样高 CPU 使用率就会消失。

我只是更改索引的顺序。我之前有 deviceId、lastPositionTime 甚至更多。 使用该顺序,索引不起作用,因此在更改后,添加除法,更改顺序并删除无用参数,索引就像魅力一样。

因此,当您在使用 mysql 时遇到 cpu 使用率高的问题时,您应该检查索引顺序,因为如果您设计的索引应该可以加快查询速度,如果没有,则可能是索引分配。

至少这解决了我的问题。

感谢大家的帮助。

【讨论】:

    【解决方案2】:

    这是我的方法,它应该在没有使用 MySQL 变量的连接的情况下一次性通过表。我在这里应用的前提如下。排序结果必须是设备 ID 和报告时间的结果。 MySQL 变量将跟踪...我是否在与最后一条记录相同的设备上工作?以及,当前记录是否在启动设备的最后一条记录的 5 分钟内,或者最后一次超过给定的速度限制被打破这就是为什么。假设我有一个给定的设备在 15 分钟内报告,从上午 8:00 开始,为了简单起见,每分钟报告一次。速度如下:

    8:00   58  -- Start group 8:00, set max time to still consider as 8:05
    8:01   60  -- speeding... within the 8:00-8:05 range.  NEW end 5 minutes from now 8:06
    8:02   58  -- not speeding
    8:03   58  -- not speeding
    8:04   59  -- not speeding
    8:05   58  -- not speeding
    8:06   59  -- not speeding... end of the 8:06 range, 1 over limit, ignore this
    8:07   60  -- NEW cycle for device, start at 8:07 set ending time to 8:12 -- SPEEDING 1
    8:08   61  -- STILL speeding max 61, extend ending time from 8:08 + 5 minutes to 8:13
    8:09   62  -- STILL speeding max 62, extend ending time from 8:09 + 5 minutes to 8:14
    8:10   59  -- not speeding
    8:11   59  -- not speeding
    8:12   60  -- SPEEDING AGAIN, within the 8:14 cutoff... reset 5 minutes from now 8:17
    8:13   62  -- speeding still, extend to 8:18
    8:14   64  -- speeding still, new max 64 and extend to 8:19
    8:15   62  -- still speeding... total times 7 with max of 64 (so far)
    

    如果不考虑上述格式,则必须计算所有记录

    8:00 to 8:05
    8:01 to 8:06
    8:02 to 8:07, etc.
    

    对于示例数据,您将从

    8:05 to 8:09
    8:06 to 8:10
    8:07 to 8:11, etc 
    

    但是这种方式会一直查看从最后一个 SPEEDING 时间开始滚动 5 分钟的截止时间。例如,如果在 8 点 20 分到 8 点 26 分之间,该人将速度降到 60 以下,那么下一次遇到超速时将开始一个新的循环。否则,考虑如果一个人在 8:00 到 10:00 之间超速行驶,将返回多少条记录。如果报告是每分钟 1 条,您将有 2*60 条记录...如果报告更频繁,它会显示得更多。

    随着设备 ID 的变化,“第一次”标识下一个超速考虑周期的开始。处理完每条记录后,将该设备 ID 放入“@lastDevice”变量中以进行下一次循环比较。如果在当前记录上没有遇到超速,@nextTimeCutoff 将与上一行相同,或者无论设备如何都添加 5 分钟...如果设备正在更改,因为它是条目的最后一个,下一行将开始一个新的“LimitSequence”无论如何都会增加 1。

    现在,说了这么多,这个查询将为您简化项目。如果您知道您关心的速度限制(60kph),我的内部查询仅预查询设备标记为超速的那些记录。如果您有 200 万条记录要通过,而且其中大多数都没有超速,那么为什么还要打扰它们。因此,内部查询仅限定那些正在加速的查询,并将它们按正确的顺序排列以进行 mysql 变量处理。它将处理从人第一次超速开始的滚动时间段,如上所述。

    select
          r1.DeviceID,
          @overLimitSeq := if( r1.DeviceID = @lastDevice 
                            AND r1.lastPositionTime <= @nextTimeCutoff,
                            @overLimitSeq, @overLimitSeq +1 ) as LimitSequence,
          min( r1.lastPositionTime ) as SpeedingTimeStart,
          max( r1.lastPositionTime ) as SpeedingTimeEnd,
          max( r1.speed ) as MaxSpeed,
          count(*) as TimesOverLimit,
          @lastDevice := r1.DeviceID as CompareForNextID,
          @nextTimeCutoff := r1.lastPositionTime + interval 5 minute as NextCutoff
       from
          ( select rp.deviceID,
                   rp.lastPositionTime,
                   rp.speed
               from
                  regPosition rp
               where
                  rp.speed >= 60
               order by
                  rp.deviceID,
                  rp.lastPositionTime ) r1,
          ( select @lastDevice := 0,
                   @overLimitSeq := 0,
                   @nextTimeCutoff := now() ) sqlvars
       group by
          r1.DeviceID,
          LimitSequence
       having
          TimesOverLimit > 2
       order by
          r1.deviceID,
          r1.lastPositionTime
    

    【讨论】:

      【解决方案3】:

      添加索引:

       ... ON (deviceId, lastPositionTime, speed) 
      

      并更改查询。替换这个谓词:

      TIMESTAMPDIFF(MINUTE, lastPositionTime,now()) <= '5'
      

      用这个:

      lastPositionTime >= NOW() + INTERVAL -5 MINUTE
      

      这将为您提供查询的覆盖索引(EXPLAIN 将显示“使用索引”),这意味着可以从索引中满足查询。

      您首先需要 deviceId 列,因为 MySQL 可以使用以该列作为前导列的索引来优化 GROUP BY 操作(避免排序操作)。

      您希望在 lastPositionTime 裸列上使用谓词,以便 MySQL 可以进行索引范围扫描。当该列隐藏在函数中时(例如 TIMESTAMPDIFF),MySQL 必须为每一行评估该函数。索引范围扫描更快,因为它可以消除对大量行的函数评估。

      由于speed 列上还有一个谓词,因此您也希望在索引中使用它。

      没有引用其他列,因此可以完全从索引中满足查询,而无需访问基础表中的页面。

      【讨论】:

      • 好的,我已经添加了索引,但我看不到 cpu 使用率有任何改善。也许我没有在索引中使用正确的设置。我使用了以下设置:索引类型:普通索引方法:BTREE 不知道这个设置是否ok。
      • 我建议您查看EXPLAIN SELECT ... 的输出以查看查询执行计划。 CPU 使用率并不一定表明出现问题……查询的执行会消耗一些 CPU,真正重要的是 CPU 实际在做什么,以及查询完成的速度。
      猜你喜欢
      • 1970-01-01
      • 2011-06-22
      • 2020-10-08
      • 2023-04-03
      • 2018-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-19
      相关资源
      最近更新 更多