优化 mysql 上的查询

Optimize Query on mysql

我有一个查询在不在内存中时运行得非常慢(15 20 秒)而在内存中时运行速度非常快(2 秒 - 0.6 秒)

select count(distinct(concat(conexiones.tMacAdres,date_format(conexiones.fFecha,'%Y%m%d')))) as Conexiones,
                    sum(if(conexiones.tEvento='megusta',1,0)) as MeGusta,sum(if(conexiones.tEvento='megusta',conexiones.nAmigos,0)) as ImpactosMeGusta,
                    sum(if(conexiones.tEvento='checkin',1,0)) as CheckIn,sum(if(conexiones.tEvento='checkin',conexiones.nAmigos,0)) as ImpactosCheckIn,
                    min(conexiones.fFecha) Fecha_Inicio, now() Fecha_fin,datediff(now(),min(conexiones.fFecha)) as dias
                    from conexiones, instalaciones
                    where  conexiones.idInstalacion=instalaciones.idInstalacion and conexiones.idInstalacion=190
                        and (fFecha between '2014-01-01 00:00:00' and '2016-06-18 23:59:59')
                    group by instalaciones.tNombre
                    order by instalaciones.idCliente

这是 Table 模式: 安装 1332 行:

CREATE TABLE `instalaciones` (
  `idInstalacion` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `idCliente` int(10) unsigned DEFAULT NULL,
  `tRouterSerial` varchar(50) DEFAULT NULL,
  `tFacebookPage` varchar(256) DEFAULT NULL,
  `tidFacebook` varchar(64) DEFAULT NULL,
  `tNombre` varchar(128) DEFAULT NULL,
  `tMensaje` varchar(128) DEFAULT NULL,
  `tWebPage` varchar(128) DEFAULT NULL,
  `tDireccion` varchar(128) DEFAULT NULL,
  `tPoblacion` varchar(128) DEFAULT NULL,
  `tProvincia` varchar(64) DEFAULT NULL,
  `tCodigoPosta` varchar(8) DEFAULT NULL,
  `tLatitud` decimal(15,12) DEFAULT NULL,
  `tLongitud` decimal(15,12) DEFAULT NULL,
  `tSSID1` varchar(40) DEFAULT NULL,
  `tSSID2` varchar(40) DEFAULT NULL,
  `tSSID2_Pass` varchar(40) DEFAULT NULL,
  `fSincro` datetime DEFAULT NULL,
  `tEstado` varchar(10) DEFAULT NULL,
  `tHotspot` varchar(10) DEFAULT NULL,
  `fAlta` datetime DEFAULT NULL,
  PRIMARY KEY (`idInstalacion`),
  UNIQUE KEY `tRouterSerial` (`tRouterSerial`),
  KEY `idInstalacion` (`idInstalacion`)
) ENGINE=InnoDB AUTO_INCREMENT=1332 DEFAULT CHARSET=utf8;

Conexiones 2370365 行

CREATE TABLE `conexiones` (
  `idConexion` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `idInstalacion` int(10) unsigned DEFAULT NULL,
  `idUsuario` int(11) DEFAULT NULL,
  `tMacAdres` varchar(64) DEFAULT NULL,
  `tUsuario` varchar(128) DEFAULT NULL,
  `tNombre` varchar(64) DEFAULT NULL,
  `tApellido` varchar(64) DEFAULT NULL,
  `tEmail` varchar(64) DEFAULT NULL,
  `tSexo` varchar(20) DEFAULT NULL,
  `fNacimiento` date DEFAULT NULL,
  `nAmigos` int(11) DEFAULT NULL,
  `tPoblacion` varchar(64) DEFAULT NULL,
  `fFecha` datetime DEFAULT NULL,
  `tEvento` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`idConexion`),
  KEY `idInstalacion` (`idInstalacion`),
  KEY `tMacAdress` (`tMacAdres`) USING BTREE,
  KEY `fFecha` (`fFecha`),
  KEY `idUsuario` (`idUsuario`),
  KEY `insta_fecha` (`idInstalacion`,`fFecha`)
) ENGINE=InnoDB AUTO_INCREMENT=2370365 DEFAULT CHARSET=utf8;

这是解释

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  instalaciones   const   PRIMARY,idInstalacion   PRIMARY 4   const   1   
1   SIMPLE  conexiones  ref idInstalacion,fFecha,insta_fecha    idInstalacion   5   const   110234  "Using where"

谢谢!

(已编辑)

显示TABLE状态'conexiones'

  Name  Engine  Version Row_format  Rows    Avg_row_length  Data_length Max_data_length Index_length    Data_free   Auto_increment  Create_time Update_time Check_time  Collation   Checksum    Create_options  Comment
 conexiones InnoDB  10  Compact 2305296 151 350060544   0   331661312   75497472    2433305 28/06/2016 22:26    NULL    NULL    utf8_general_ci NULL        

尝试使用多列索引:

CREATE idx_nn_1 ON conexiones(idInstalacion,fFecha);

根据数据,您可能需要反过来,因此请同时测试两者。这避免了读取 fFecha 匹配 idInstalacion 条件的所有记录,并且应该提高性能。

尝试以下操作:

要么删除 idInstalacion INDEX,要么告诉引擎在 from 子句中使用正确的键:

from conexiones use index (insta_fecha), instalaciones

而且您不需要 JOIN、GROUP 或 ORDER。您正在加入一行的常数值 (190)。而且您不使用 instalaciones.

中的任何列

所以你只需要这个:

select count(distinct(concat(conexiones.tMacAdres,date_format(conexiones.fFecha,'%Y%m%d')))) as Conexiones,
    sum(if(conexiones.tEvento='megusta',1,0)) as MeGusta,sum(if(conexiones.tEvento='megusta',conexiones.nAmigos,0)) as ImpactosMeGusta,
    sum(if(conexiones.tEvento='checkin',1,0)) as CheckIn,sum(if(conexiones.tEvento='checkin',conexiones.nAmigos,0)) as ImpactosCheckIn,
    min(conexiones.fFecha) Fecha_Inicio, now() Fecha_fin,datediff(now(),min(conexiones.fFecha)) as dias
    from conexiones -- use index (insta_fecha)
    where conexiones.idInstalacion=190
    and (fFecha between '2014-01-01 00:00:00' and '2016-06-18 23:59:59')

但是-这并不意味着它会更快。 MySQL 可能会优化所有这些东西。

为什么它这么慢。我将以 可能 加速结束。

首先请做

SELECT COUNT(*) FROM conexiones
    WHERE idInstalacion=190
      and fFecha >= '2014-01-01'
      and fFecha  < '2016-06-19

为了查看我们正在处理多少行。 EXPLAIN 建议 110234,但这只是粗略估计。

假设查询中涉及 conexiones 的 110K 行,并假设这些行(大约)按 fFecha 的时间顺序插入,那么...

  • 有很多行要处理,并且
  • 它们分散在磁盘上的 table 周围,因此
  • 查询占用大量I/O,除非缓存。

让我们进一步检查我最后的声明...你有多少内存? innodb_buffer_pool_size 的值是多少?它应该是 可用 RAM 的大约 70%。 如果您的 RAM 少于 4GB,请使用较低的百分比。

假设 conexiones 太大而无法在 'buffer_pool' 中达到 'cached',我们需要找到一种方法来减小 I/O。

idInstalacion 有 1332 个不同的值。也许您每隔 minutes/hours 就将 1332 行插入到 conexiones 中?由于 PRIMARY KEY 只是一个 AUTO_INCREMENT,这些行将 'appended' 到 table.

的末尾

现在让我们看看 idInstalacion=190 行在哪里。每 1332(或左右)行出现一个新的。这意味着它们是分散的。这意味着(可能)没有两行在同一个块中(InnoDB 中为 16KB)。这意味着 110234 将在 110234 个不同的块中。那大约是2GB。如果buffer_pool小于那个,那么变成I/O。即使它比这更大,也有很多数据要触及。

但是怎么办呢?如果我们可以安排 =190 行在 table 中连续排列,那么 2GB 可能会下降到 20MB —— 一个更易于管理和缓存的大小。但是那怎么办呢?通过更改 PRIMARY KEY.

PRIMARY KEY(idInstalacion, fFecha, idConexion),
INDEX(idConexion)

DROPidInstalacionidConexion 开头的任何其他索引。解释一下:

  • 由于 PK 与数据 "clustered",任何连续 fFetcha 范围内的所有 idInstalacion=190 行在数据中都是连续的。因此,获取一个块将获得大约 100 行——更少 I/O.
  • PK 必须 是独一无二的。假设 (idInstalacion, fFecha) 不是唯一的,我添加了 idConexion 使其唯一。
  • 我添加了 INDEX(idConexion)AUTO_INCREMENT 开心。

潜在的缺点...由于此更改重新排列了数据的顺序,其他查询,包括 INSERTs 可能 会变慢。 INSERTs 会分散,但不会真正减速。 1332 "hots spots" 将接受新行;可以很容易地缓存很多块。

算术...如果您有旋转驱动器,我预计现有结构对于 110234 行大约需要 1102 秒(SSD 可能不到 110 秒)。由于它花费的时间不到 20 秒,我怀疑有一些缓存(或者你有 SSD)或者 110234 被严重高估了。我建议的更改应该会显着减少 "worst" 时间,并略微改善 "in memory" 时间。 "slight improvement" 来自能够使用 PK 而不是辅助密钥。

警告:由于 110234 * 1332 与 2370365 相差甚远,因此我的大部分数值分析可能都不正确。例如,具有该架构的 2370365 行可能小于 1GB。请提供 SHOW TABLE STATUS LIKE 'conexiones'.

附录

"server has 2GB Ram and innodb_buffer_pool_size is 5368709120" -- 要么是打字错误,要么是糟糕。由于 buffer_pool 需要驻留在 RAM 中,因此不要将 buffer_pool 设置为 5GB。 500MB 可能适合你的 2GB 内存。

SHOW TABLE STATUS 确认它(数据 + 索引)不会完全适合 500M,因此您可能会定期遇到 I/O 500M 的绑定查询。

增加 RAM 和 buffer_pool 会暂时(直到数据变大)有助于提高性能。

在将其投入生产之前,测试 ALTER 并为您使用的各种查询计时:

ALTER TABLE conexiones
    DROP PRIMARY KEY,
    DROP INDEX insta_fecha,
    DROP INDEX idInstalacion,
    PRIMARY KEY(idInstalacion, fFecha, idConexion),
    INDEX(idConexion)

注意:ALTER 需要大约 1GB 的可用磁盘 space。

计时时,运行关闭查询缓存,运行两次——第一次可能涉及I/O;第二个是你提到的'in memory'。

修改后的分析:由于较大的 table 有 300MB 的数据和一些正在使用的索引,假设有 500MB 的缓冲池,我怀疑有时块会从缓冲池中弹出。这非常符合您对查询速度的初步评论。我建议的索引更改 应该 有助于避免速度差异,但 可能 会损害其他查询的性能。