MySQL 如何管理其与索引相关的内存?

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)

Ric*_*mes 5

什么指标?你没有索引!所以任何查询都会扫描整个表——所有分区。一旦整个表大于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.