web-dev-qa-db-fra.com

Comment MySQL gère-t-il sa mémoire en ce qui concerne les index?

Pour commencer, la raison pour laquelle je pose cela, c'est parce que j'ai l'impression d'avoir une base de données que - selon mes propres estimations - aurait dû tuer les disques avec des E/S massifs, en raison des indices qui ne correspondent pas à la mémoire, mais dans Actualité, il fonctionne toujours bien.

Commençons par la table correspondante:

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;

La colonne a est un chiffre de 8 octets qui a l'horodatage (en secondes) codé dessus. La table a une PARTITION BY RANGE (a), qui sépare la table en partitions mensuelles. En effet, nous gardons seulement 24 mois dans la base de données et le reste est purgé.

La table augmente d'environ 200 millions de lignes par mois; La table complète contient environ 5 milliards de lignes.

Le serveur qu'il s'exécute sur environ 360 Go de mémoire et 300 Go de cela est réservé à MySQL. Ce que je trouve intéressant, c'est cela il y a quelque temps, l'utilisation du disque a commencé à monter un peu. Je pense que cela est dû à certains index qui ne sont plus adaptés à la mémoire, ce qui provoque la charge MySQL de les charger du disque, mais ceci est juste une supposition; Je suis inconnu avec les internes de MySQL.

Existe-t-il un moyen de voir quelles pages/blocs sont chargées en mémoire à un moment donné ou pour une requête spécifique?


Ce sont les trois tables étant réellement utilisées:

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

Ce sont les requêtes qui fonctionnent actuellement:

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
4
Aeveus

Quels index? Vous n'avez pas d'index! Donc, toute requête scannera toute la table - toutes les partitions. Une fois que toute la table est plus grande que innodb_buffer_pool_size, Une numérisation de table ne se terminera pas sans avoir à toucher le disque. Et la numérisation de la table suivante aura tout tout du disque.

Un indice n'a pas besoin d'être conservé en mémoire. Il agit comme une table - il est composé de blocs de 16 ko mis en cache dans la piscine tampon au besoin, puis s'est heurté lors de "vieux" (pensez des systèmes de mise en cache "les moins récemment utilisés").

Encore une fois, si vous faites une analyse complète , et l'index ne va pas dans le pool tampon, le cache deviendra inutile et vous frapperez le disque tout le temps.

Mais ... la définition appropriée et l'utilisation, d'index ne doivent pas se retrouver avec ce destin. J'ai vu des tables de taille térabyte fonctionnant bien en 32 Go de RAM. En particulier, une "requête ponctuelle" (... WHERE primary_key = constant ...) Prendra moins de 1 seconde, quelle que soit la taille de la table ou de la petite taille du tampon_pool. Au pire (cache froid), une table d'un milliard de lignes peut avoir besoin d'aller 5 pâtés de maisons dans le BTRee pour trouver la ligne unique que vous demandez.

PARTITION BY RANGE(id) est presque toujours inutile. Au lieu de cela, PRIMARY KEY(id), sans partitionnement, un meilleur travail de localisation d'une ligne par id.

Il existe des outils pour regarder ce qui se trouve dans le tampon_pool, mais je détesterais à gérer 20 millions de numéros de blocs pour traiter ce que vous demandez!

Constroyons plutôt votre SHOW CREATE TABLE (Afin que nous puissions voir index/partitions) et quelques-uns SELECTs. De ceux que nous pouvons discuter de ce qui se passe sous les couvertures. Cela peut être beaucoup plus rapide et plus informatif.

Voir aussi ( mon livre de recettes sur la création d'index optimaux. Voir ( Mon blog de partition Pour l'utilitaire limité de PARTITIONing.

3
Rick James