扩展 MySQL 数据库,提高许多连接的性能
Posted
技术标签:
【中文标题】扩展 MySQL 数据库,提高许多连接的性能【英文标题】:Scaling MySQL database, increasing performance to many connections 【发布时间】:2015-06-23 23:25:55 【问题描述】:我有一个监控系统,我的客户可以在其中注册他们的终端,并且他的终端会定期(5 分钟)向我的网站发送一个保活信号以通知它在线。客户还可以访问显示他所有终端的监控页面,并以 20 秒的间隔使用 ajax 更新其状态。
附加信息:终端是安卓设备,客户必须从google play安装一个应用程序。
问题是: 随着客户数量的增加,许多人同时访问监控页面,这几乎使服务器充满了许多请求,而另一方面。每次都有更多的终端涌入并充斥着更多的keepalive信号。因此,除了常见页面(登录、许多 CRUD 等)之外,我还有几十个通过互联网发送保活信号的物理终端淹没了我的数据库,并且许多用户访问监控页面以了解他们的终端在线。这似乎是一个定时炸弹。因为我不知道当终端数量达到数百并且还在增加时,mysql是否支持。
PLUS我们已经注意到我们的服务器在运行时会降低性能。我们重新启动它,它非常快,但随着时间的推移,它会失去性能
解决方案
我可以做些什么来提高性能或使模型更具可扩展性?这种监控系统有没有一种扩展性更强的设计模式?
如果我分离两个 mysql 数据库,一个用于常用(访问页面、cruds 等),另一个用于监控系统,会有什么好处?
仅将 MongoDB 用于系统的监控部分有什么好处吗?
其他信息:
mysql Ver 14.14 Distrib 5.5.43,适用于 Linux (x86_64),使用 readline 5.1
php 5.4.40 (cli)(构建时间:2015 年 4 月 15 日 15:55:28)
Jetty 8.1.14(用于与 android 应用程序通信的 java 服务器端)
服务器周一
Free memory ........: 17.84 Gb
Total memory........: 20 Gb
Used memory.........: 2.16 Gb
RAM.................: 20 Kb
JVM Free memory.....: 1.56 Gb
JVM Maximum memory..: 3.93 Gb
JVM Total available.: 1.93 Gb
**************************************
Total (cores).: 10
CPU idle......: 4.9%
CPU nice......: 0.0%
CPU system....: 4183000.0%
CPU total.....: 5.0%
CPU user......: 2.6%
**************************************
Total space (bytes)..: 600 Gb
Free space (bytes)...: 595.64 Gb
Usable space (bytes).: 595.64 Gb
模型和监控页面查询的一部分
这是终端表
CREATE TABLE IF NOT EXISTS `GM_PLAYER` (
`ID_PLAYER` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`DS_GCM_ID` VARCHAR(250) NULL,
`DT_CRIACAO` DATETIME NOT NULL,
`DS_PLAYER` VARCHAR(100) NOT NULL,
`DS_JANELA_HEIGHT` INT(11) NOT NULL DEFAULT '1024',
`DS_JANELA_WIDTH` INT(11) NOT NULL DEFAULT '768',
`DS_JANELA_POS_X` INT(11) NOT NULL DEFAULT '0',
`DS_JANELA_POS_Y` INT(11) NOT NULL DEFAULT '0',
`DS_WALLPAPER` VARCHAR(255) NULL DEFAULT NULL,
`FL_ATIVO` CHAR(1) NOT NULL DEFAULT 'N',
`FL_FULL_SCREEN` CHAR(1) NOT NULL DEFAULT 'S',
`FL_MOUSE_VISIBLE` CHAR(1) NOT NULL DEFAULT 'N',
`DS_SERIAL` VARCHAR(50) NULL DEFAULT NULL,
`VERSAO_APP` VARCHAR(20) NULL DEFAULT NULL,
`VERSAO_OS` VARCHAR(20) NULL DEFAULT NULL,
`FL_EXIBIR_STATUS_BAR` CHAR(1) NOT NULL DEFAULT 'S',
`ID_GRADE_PROGRAMACAO` BIGINT UNSIGNED NULL DEFAULT NULL,
`ID_CLIENTE` BIGINT UNSIGNED NULL,
`ID_PONTO` BIGINT UNSIGNED NULL,
`FL_ATIVO_SISTEMA` CHAR(1) NOT NULL DEFAULT 'S',
`FL_DEBUG` CHAR(1) NOT NULL DEFAULT 'N',
`VERSAO_APP_UPDATE` VARCHAR(20) NULL,
`FL_ESTADO_MONITOR` CHAR(1) NOT NULL DEFAULT 'L',
`FL_DEVICE_ROOTED` CHAR(1) DEFAULT 'N',
`DT_ATIVACAO` DATETIME ,
`DT_EXPIRA` DATETIME ,
`FL_EXCLUIDO` CHAR(1) DEFAULT 'N' ,
`ID_USUARIO` BIGINT UNSIGNED NOT NULL,
`ID_PACOTE` BIGINT UNSIGNED ,
`DS_IMG_BARRA` VARCHAR(255),
`FL_EXIBIR_HORA` CHAR(1),
`DS_TEXTO_BARRA` TEXT,
PRIMARY KEY (`ID_PLAYER`),
UNIQUE INDEX `UQ_GM_PLAYER_ID_PLAYER` (`ID_PLAYER` ASC),
INDEX `ID_GRADE_PROGRAMACAO` (`ID_GRADE_PROGRAMACAO` ASC),
INDEX `FK_GM_PLAYER_GM_CLIENTE_idx` (`ID_CLIENTE` ASC),
CONSTRAINT `FK_GM_PLAYER_GM_USUARIO` FOREIGN KEY (`ID_USUARIO`) REFERENCES `GM_USUARIO` (`ID_USUARIO`) ON DELETE RESTRICT,
CONSTRAINT `FK_GM_PLAYER_GM_GRADE_PROGRAMACAO` FOREIGN KEY (`ID_GRADE_PROGRAMACAO`) REFERENCES `GM_GRADE_PROGRAMACAO` (`ID_GRADE_PROGRAMACAO`) ON DELETE RESTRICT,
CONSTRAINT `FK_GM_PLAYER_GM_CLIENTE` FOREIGN KEY (`ID_CLIENTE`) REFERENCES `GM_CLIENTE` (`ID_CLIENTE`) ON DELETE RESTRICT
)
ENGINE = InnoDB
AUTO_INCREMENT = 5
DEFAULT CHARACTER SET = latin1;
另一个用过的表
CREATE TABLE IF NOT EXISTS `GM_CLIENTE` (
`ID_CLIENTE` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`DT_CRIACAO` DATETIME NOT NULL,
`DS_CLIENTE` VARCHAR(50) NOT NULL,
`FL_ATIVO` ENUM('S','N') NULL DEFAULT 'S',
`ID_CONTATO` BIGINT UNSIGNED NOT NULL,
`ID_ENDERECO` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`ID_CLIENTE`),
UNIQUE INDEX `UQ_Cliente_ID_CLIENTE` (`ID_CLIENTE` ASC),
INDEX `fk_GM_CLIENTE_GM_CONTATO1_idx` (`ID_CONTATO` ASC),
INDEX `fk_GM_CLIENTE_GM_ENDERECO1_idx` (`ID_ENDERECO` ASC),
CONSTRAINT `fk_GM_CLIENTE_GM_CONTATO1`
FOREIGN KEY (`ID_CONTATO`)
REFERENCES `GM_CONTATO` (`ID_CONTATO`)
ON DELETE RESTRICT,
CONSTRAINT `fk_GM_CLIENTE_GM_ENDERECO1`
FOREIGN KEY (`ID_ENDERECO`)
REFERENCES `GM_ENDERECO` (`ID_ENDERECO`)
ON DELETE RESTRICT)
ENGINE = InnoDB
AUTO_INCREMENT = 2
DEFAULT CHARACTER SET = latin1;
CREATE TABLE GM_USUARIO_CLIENTE (
ID_USUARIO_CLIENTE INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
ID_CLIENTE BIGINT UNSIGNED ,
ID_USUARIO BIGINT UNSIGNED
);
这是我每次收到新的终端保活信号时都会更新的表格
CREATE TABLE IF NOT EXISTS `GM_LOG_PLAYER` (
`id_log_player` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`dt_criacao` DATETIME NOT NULL,
`id_player` BIGINT UNSIGNED NULL,
`qtd_midias_exibidas` INT(11) NULL,
`id_ultima_midia_exibida` BIGINT UNSIGNED NULL,
`up_time_android` bigint(20) unsigned default '0',
`up_time_app` bigint(20) unsigned default '0',
`mem_utilizada` BIGINT(20) NULL,
`mem_disponivel` BIGINT(20) NULL,
`hd_disponivel` BIGINT(20) NULL,
`hd_utilizado` BIGINT(20) NULL,
PRIMARY KEY (`id_log_player`),
UNIQUE INDEX `UQ_id_log_player` (`id_log_player` ASC),
INDEX `FK_GM_LOG_PLAYER_GM_PLAYER_idx` (`id_player` ASC),
INDEX `FK_GM_LOG_PLAYER_GM_MIDIA_idx` (`id_ultima_midia_exibida` ASC),
CONSTRAINT `FK_GM_LOG_PLAYER_GM_PLAYER`
FOREIGN KEY (`id_player`)
REFERENCES `GM_PLAYER` (`ID_PLAYER`)
ON DELETE CASCADE,
CONSTRAINT `FK_GM_LOG_PLAYER_GM_MIDIA`
FOREIGN KEY (`id_ultima_midia_exibida`)
REFERENCES `GM_MIDIA` (`ID_MIDIA`))
ENGINE = InnoDB
AUTO_INCREMENT = 3799
DEFAULT CHARACTER SET = latin1;
CREATE TABLE IF NOT EXISTS `GM_GRADE_PROGRAMACAO` (
`ID_GRADE_PROGRAMACAO` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`DT_CRIACAO` DATETIME NOT NULL,
`DS_GRADE_PROGRAMACAO` VARCHAR(100) NULL DEFAULT NULL,
`ID_USUARIO` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`ID_GRADE_PROGRAMACAO`),
UNIQUE INDEX `UQ_GM_GRADE_PROGRAMACAO_ID_GRADE_PROGRAMACAO` (`ID_GRADE_PROGRAMACAO` ASC),
INDEX `fk_GM_GRADE_PROGRAMACAO_GM_USUARIO1_idx` (`ID_USUARIO` ASC),
CONSTRAINT `fk_GM_GRADE_PROGRAMACAO_GM_USUARIO1`
FOREIGN KEY (`ID_USUARIO`)
REFERENCES `GM_USUARIO` (`ID_USUARIO`)
ON DELETE RESTRICT)
ENGINE = InnoDB
AUTO_INCREMENT = 3
DEFAULT CHARACTER SET = latin1;
这是通过ajax请求定期执行的查询来更新监控页面
SELECT * FROM (
SELECT
LOG.id_log_player ,
LOG.dt_criacao ,
DATE_FORMAT (LOG.DT_CRIACAO , '%d/%m/%Y %H:%i:%s') F_DT_CRIACAO ,
(CURRENT_TIMESTAMP - LOG.DT_CRIACAO) AS IDADE_REGISTRO ,
LOG.qtd_midias_exibidas ,
LOG.id_ultima_midia_exibida ,
LOG.up_time_android ,
LOG.up_time_app ,
LOG.mem_utilizada ,
LOG.mem_disponivel ,
LOG.hd_disponivel ,
LOG.hd_utilizado ,
PLA.FL_MONITOR_LIGADO,
CLI.DS_CLIENTE ,
PLA.ID_PLAYER id_player ,
PLA.DS_PLAYER ,
PLA.ID_CLIENTE ,
PLA.VERSAO_APP ,
PLA.FL_ATIVO PLA_FL_ATIVO ,
PLA.ID_GRADE_PROGRAMACAO ,
PLA.FL_DEVICE_ROOTED ,
PLA.DS_GCM_ID ,
PLA.FL_HDMI_LIGADO ,
-- IF(PLA.FL_ATIVO='N',0,IF(PLA.ID_GRADE_PROGRAMACAO IS NULL,0,IF(PLA.ID_GRADE_PROGRAMACAO='0',0,1))) ATIVO,
IF(PLA.FL_ATIVO='N',0,1) ATIVO,
DATE_FORMAT (LOG.DT_CRIACAO , '%Y%m%d%H%i%s') TIME_STAMP_CRIACAO ,
DATE_FORMAT (LOG.DT_CRIACAO , '%d/%m às %H:%i') F_DT_CRIACAO_MIN ,
-- (CURRENT_TIMESTAMP - LOG.DT_CRIACAO) ESPERA_NOVA_COMUNICACAO ,
--GRA.ID_GRADE_PROGRAMACAO GRA_ID_GRADE ,
GRA.DS_GRADE_PROGRAMACAO GRA_DS_GRADE_PROGRAMACAO,
MID.DS_PATH_THUMB THUMB_ULTMID
FROM GM_PLAYER PLA
LEFT JOIN GM_CLIENTE CLI USING ( ID_CLIENTE )
LEFT JOIN GM_USUARIO_CLIENTE GUC USING ( ID_CLIENTE )
LEFT JOIN GM_LOG_PLAYER LOG USING ( ID_PLAYER )
LEFT JOIN GM_GRADE_PROGRAMACAO GRA USING ( ID_GRADE_PROGRAMACAO )
-- LEFT JOIN GM_GRADE_PROGRAMACAO GRA ON ( PLA.ID_GRADE_PROGRAMACAO = GRA.ID_GRADE_PROGRAMACAO )
LEFT JOIN GM_MIDIA MID ON ( LOG.ID_ULTIMA_MIDIA_EXIBIDA = MID.ID_MIDIA )
WHERE PLA.ID_USUARIO = ?
AND PLA.FL_EXCLUIDO = 'N'
AND PLA.FL_ATIVO = 'S'
ORDER BY LOG.DT_CRIACAO DESC
) TBALL
GROUP BY ID_PLAYER
ORDER BY PLA_FL_ATIVO DESC , DT_CRIACAO DESC
EXPLAIN QUERY ABOVE(取自开发数据库)
+----+-------------+------------+--------+------------------------------------------------------+----------------------------------------------+---------+--------------------------------------+-------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+------------------------------------------------------+----------------------------------------------+---------+--------------------------------------+-------+----------------------------------------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 37752 | Using temporary; Using filesort |
| 2 | DERIVED | PLA | ALL | NULL | NULL | NULL | NULL | 44 | Using where; Using temporary; Using filesort |
| 2 | DERIVED | CLI | eq_ref | PRIMARY,UQ_Cliente_ID_CLIENTE | PRIMARY | 8 | imidiatv.PLA.ID_CLIENTE | 1 | NULL |
| 2 | DERIVED | GUC | ref | fk_GM_CLIENTE_has_GM_USUARIO_GM_CLIENTE1_idx | fk_GM_CLIENTE_has_GM_USUARIO_GM_CLIENTE1_idx | 8 | imidiatv.PLA.ID_CLIENTE | 1 | Using index |
| 2 | DERIVED | LOG | ref | FK_GM_LOG_PLAYER_GM_PLAYER_idx | FK_GM_LOG_PLAYER_GM_PLAYER_idx | 9 | imidiatv.PLA.ID_PLAYER | 858 | NULL |
| 2 | DERIVED | GRA | eq_ref | PRIMARY,UQ_GM_GRADE_PROGRAMACAO_ID_GRADE_PROGRAMACAO | PRIMARY | 8 | imidiatv.PLA.ID_GRADE_PROGRAMACAO | 1 | NULL |
| 2 | DERIVED | MID | eq_ref | PRIMARY,UQ_GM_MIDIA_ID_MIDIA | PRIMARY | 8 | imidiatv.LOG.id_ultima_midia_exibida | 1 | NULL |
+----+-------------+------------+--------+------------------------------------------------------+----------------------------------------------+---------+--------------------------------------+-------+----------------------------------------------+
提前致谢
【问题讨论】:
有多少个终端发送keyalive? (从中我可以计算出每秒有多少查询。)监控也是如此。监控SELECT
是什么样的?还有SHOW CREATE TABLE
?
我在钓鱼 (1) 是不是有太多的 qps 是问题所在,还是 (2) 是查询效率低下,应该改进。
@RickJames,早上好!今天我只有大约 80 个终端,但我希望它在一年内支持大约 1000 个。您请求的 SELECTs 和 CREATE TABLE 现已发布
【参考方案1】:
部分回答...
扩展的一个方面是最小化磁盘占用空间,以便缓存更有效。为此,这里有一些建议:
PRIMARY KEY (`id_log_player`),
UNIQUE INDEX `UQ_id_log_player` (`id_log_player` ASC),
PRIMARY KEY
是一个UNIQUE
键,所以后者是冗余的并且浪费磁盘空间和INSERT
时间。 DROP
它。
INT
是 4 个字节; BIGINT
是 8 个字节。 ID_xx INT UNSIGNED
可以处理多达 40 亿个值;你真的需要超过40亿吗?在 InnoDB 中,每个辅助键都包含一个 PRIMARY KEY
的副本,这意味着不必要的 BIGINT
PK 会消耗更多空间。
您的桌子是latin1
;您是否将应用程序限制为西方语言?如果你改成utf8(或utf8mb4),我会指出CHAR(1)
的空间浪费。
请按原样执行EXPLAIN SELECT ...
;然后在下面进行一些更改并再次执行EXPLAIN
。我认为差异可能是戏剧性的。我期待处理的部分
LEFT JOIN GM_USUARIO_CLIENTE GUC USING ( ID_CLIENTE )
相当“戏剧化”。
如果GM_USUARIO_CLIENTE
是“多对多”映射,...
AUTO_INCREMENT
;而是使用PRIMARY KEY(ID_CLIENTE, ID_USUARIO)
来节省一些空间并提高效率。 (如果您确实超过 40 亿客户等,INT
就不够了!)
添加两个索引以便查找更快。 (1) PK(上图),和 (2) 另一个方向:INDEX(ID_USUARIO, ID_CLIENTE)
。如果没有这些,涉及该表的JOINs
会随着您的扩展而变得越来越慢。
日期算术并非如此简单:
(CURRENT_TIMESTAMP - LOG.DT_CRIACAO)
研究日期函数的手册页;减去TIMESTAMP - DATETIME
更复杂。如果您要跨越时区,请注意使用哪种数据类型。
我看到了这种模式:
SELECT * FROM (
SELECT ...
ORDER BY ... -- ??
) x
GROUP BY ...
您希望实现什么目标?优化器可以随意忽略子查询中的ORDER BY
。 (虽然,它实际上可能正在执行它。)
除非你有理由,否则不要使用LEFT
。
本条款
WHERE PLA.ID_USUARIO = ?
AND PLA.FL_EXCLUIDO = 'N'
AND PLA.FL_ATIVO = 'S'
会从INDEX(ID_USUARIO, FL_EXCLUIDO, FL_ATIVO)
中受益(很大?)。索引中列的顺序(在这种情况下)无关紧要。如果这两个标志经常更改,请不要将它们包含在INDEX
中——UPDATEs
的速度可能会比SELECTs
受益更多。
这些是易于发现的建议。 EXPLAIN
可能有助于发现更多建议。你还有其他SELECTs
吗?
我说的是“部分解决方案”。那是SELECT
“监控选择”吗?让我们也检查一下定期UPDATEs
。
【讨论】:
Rick James,定期更新只是对 GM_LOG_PLAYER 的更新,它几乎只更新终端状态、日志日期和一些 ids/flags。此更新不是由 PHP 端完成的。我必须要求 Java 程序员给我查询,但是我从开发数据库中获取的解释并要添加到我的评论中 监控是否每次都扫描所有玩家的所有日志条目?这似乎太过分了。每个玩家都有数百甚至数千条日志条目?问题在于监控的设计。每次获取监控时,您需要获取更少的日志条目或更少的用户。 LOG可以保存活动历史;没关系。但是有另一个表格可以为您提供每个玩家的当前 状态——每个玩家一个 行。当您将INSERT
写入LOG 时,UPDATE
这个新表会反映当前状态。重新设计将使您的可扩展性提高 100 倍。
行 WHERE PLA.ID_USUARIO = ?将按用户过滤,我只从一个用户那里获取所有日志,而不是针对所有用户。第一个实现,终端在数据库上插入。但它正在填充我们的旧无用数据的数据库,所以我们将其更改为仅更新,今天,每个终端在 GM_LOG_PLAYER 表上只有一行。并且在每个保活信号上都会更新。我们现在不关心历史。
我的立场是正确的。它在 1 个用户和 1 个日志行的情况下运行得更快吗?建议在您的问题中添加更新信息,或将此主题标记为已解决,而不是将其混乱解释等。以上是关于扩展 MySQL 数据库,提高许多连接的性能的主要内容,如果未能解决你的问题,请参考以下文章