Aev*_*eus 5 mysql memory database-internals percona mysql-5.6
首先,我问这个的原因是因为我觉得我有一个数据库 - 根据我自己的估计 - 应该用大量 I/O 杀死磁盘,因为索引不适合内存,但在实际上它仍然表现良好。
让我们从相关表开始:
CREATE TABLE `search` (
`a` bigint(20) unsigned NOT NULL,
`b` int(10) unsigned NOT NULL,
`c` int(10) unsigned DEFAULT NULL,
`d` int(10) unsigned DEFAULT NULL,
`e` varchar(255) DEFAULT NULL,
`f` varchar(255) DEFAULT NULL,
`g` varchar(255) DEFAULT NULL,
`h` varchar(255) DEFAULT NULL,
`i` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Run Code Online (Sandbox Code Playgroud)
该a列是一个 8 字节的数字,其中编码了时间戳(以秒为单位)。该表有一个PARTITION BY RANGE (a), 将表分成每月分区。这是因为我们只在数据库中保留了 24 个月,其余的都被清除了。
该表每月增长约 2 亿行;整个表包含大约 50 亿行。
它运行的服务器有大约 360GB 的内存,其中 300GB 是为 MySQL 保留的。我觉得有趣的是,不久前,磁盘利用率开始有所上升。现在,我相信这是因为某些索引不再适合内存,导致 MySQL 从磁盘加载它们,但这只是一个猜测;我不熟悉 MySQL 的内部结构。
有没有办法查看在给定时间或针对特定查询将哪些页面/块加载到内存中?
这些是实际使用的三个表:
CREATE TABLE `search` (
`a` bigint(20) unsigned NOT NULL,
`b` int(10) unsigned NOT NULL,
`c` int(10) unsigned DEFAULT NULL,
`d` int(10) unsigned DEFAULT NULL,
`e` varchar(255) DEFAULT NULL,
`f` varchar(255) DEFAULT NULL,
`g` varchar(255) DEFAULT NULL,
`h` varchar(255) DEFAULT NULL,
`i` varchar(255) DEFAULT NULL,
KEY `a_idx` (`a`),
KEY `b_idx` (`b`),
KEY `c_idx` (`c`, `a`),
KEY `d_idx` (`d`, `a`),
KEY `e_idx` (`e`, `a`),
KEY `f_idx` (`f`, `a`),
KEY `g_idx` (`g`, `a`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `channels` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `clients` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`client_hash` varchar(4095) NOT NULL,
PRIMARY KEY (`id`),
KEY `hash_idx` (`client_hash`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Run Code Online (Sandbox Code Playgroud)
这些是当前正在运行的查询:
SELECT S.a,
S.b,
S.e,
S.f,
S.g,
S.h,
S.i,
C1.client_hash,
C2.name
FROM search S
LEFT JOIN clients C1
ON S.c = C1.id
LEFT JOIN channels C2
ON S.d = C2.id
WHERE S.e = "foo"
AND S.a >= 6409642363135721472
AND S.a <= 6443039964404908032
AND S.b >= 1492361157
AND S.b <= 1500137142
ORDER BY S.a DESC
LIMIT 50
SELECT S.a,
S.b,
S.e,
S.f,
S.g,
S.h,
S.i,
C1.client_hash,
C2.name
FROM search S
LEFT JOIN clients C1
ON S.c = C1.id
LEFT JOIN channels C2
ON S.d = C2.id
WHERE S.f = "bar"
AND S.a >= 6409642363135721472
AND S.b >= 1492361157
ORDER BY S.a DESC
LIMIT 50
SELECT S.a,
S.b,
S.e,
S.f,
S.g,
S.h,
S.i,
C1.client_hash,
C2.name
FROM search S
LEFT JOIN clients C1
ON S.c = C1.id
LEFT JOIN channels C2
ON S.d = C2.id
WHERE S.g = "baz"
AND S.a >= 6409642363135721472
AND S.b >= 1492361157
ORDER BY S.a DESC
LIMIT 50
SELECT S.a,
S.b,
S.e,
S.f,
S.g,
S.h,
S.i,
C1.client_hash,
C2.name
FROM search S
LEFT JOIN clients C1
ON S.c = C1.id
LEFT JOIN channels C2
ON S.d = C2.id
WHERE S.g LIKE "baz%"
AND S.a >= 6409642363135721472
AND S.b >= 1492361157
ORDER BY S.a DESC
LIMIT 50
Run Code Online (Sandbox Code Playgroud)
什么指标?你没有索引!所以任何查询都会扫描整个表——所有分区。一旦整个表大于innodb_buffer_pool_size,表扫描将无法完成而不必命中磁盘。下一次表扫描将从磁盘中重新读取所有内容。
索引不需要保存在内存中。它的作用就像一张表——它由 16KB 块组成,这些块根据需要缓存到缓冲池中,然后在“旧”时弹出(想想“最近最少使用”的缓存方案)。
同样,如果您执行完整索引扫描,并且索引将无法放入缓冲池,那么缓存将变得无用,您将一直访问磁盘。
但是... 索引的正确定义和使用不一定以这种命运告终。我已经看到 TB 大小的表在 32GB 的 RAM 中运行良好。特别是“点查询”( ... WHERE primary_key = constant ...) 将花费不到 1 秒的时间,无论表有多大或 buffer_pool 有多小。在最坏的情况下(冷缓存),一个 10 亿行的表可能需要在 BTree 中获取 5 个块才能找到您要求的单行。
PARTITION BY RANGE(id)几乎总是没用的。相反,在PRIMARY KEY(id)不分区的情况下,可以更好地按 定位行id。
有一些工具可以查看 buffer_pool 中的内容,但我不想处理 2000 万个块号来处理您的要求!
相反,让我们看看您的实际SHOW CREATE TABLE(以便我们可以看到索引/分区)和一些SELECTs. 从这些我们可以讨论幕后发生的事情。这可能会更快,信息更多。
另请参阅我关于创建最佳索引的食谱。请参阅我的分区博客,了解PARTITIONing.
| 归档时间: |
|
| 查看次数: |
3171 次 |
| 最近记录: |