优化 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)
和 DROP
以 idInstalacion
或 idConexion
开头的任何其他索引。解释一下:
- 由于 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 的缓冲池,我怀疑有时块会从缓冲池中弹出。这非常符合您对查询速度的初步评论。我建议的索引更改 应该 有助于避免速度差异,但 可能 会损害其他查询的性能。
我有一个查询在不在内存中时运行得非常慢(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)
和 DROP
以 idInstalacion
或 idConexion
开头的任何其他索引。解释一下:
- 由于 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 的缓冲池,我怀疑有时块会从缓冲池中弹出。这非常符合您对查询速度的初步评论。我建议的索引更改 应该 有助于避免速度差异,但 可能 会损害其他查询的性能。