Raisons des requêtes parfois lentes?

16

Nous exécutons MySQL 5.1 sur Windows Server 2008 R2.

Nous avons récemment effectué des diagnostics sur notre base de données et avons trouvé des artefacts dérangeants que nous ne pouvons pas expliquer . Nous avons ajouté du code pour nous connecter lorsque nous avions des requêtes qui prenaient beaucoup de temps (> 2000 ms). Les résultats étaient surprenants (et peut-être une explication de nos blocages).

Parfois, les requêtes, qui prennent normalement très peu de temps (<10 ms), prennent de 4 à 13 secondes. Pour être clair, ce sont des requêtes qui s'exécutent en permanence (plusieurs fois par seconde) et ne souffrent pas de ces pics de temps de requête.

Nous avons parcouru nos index à la recherche d'erreurs évidentes et n'avons pas eu beaucoup de chance.

Mise à jour

La table des personnes:

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

Index:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

nous avons ~ 5000 lignes dans la table sur le serveur qui nous pose problème.

RedBlueThing
la source
1
Il y a quelque chose que vous n'avez pas encore montré dans les deux questions précédentes. Veuillez ajouter à cette question trois (3) choses: 1) AFFICHER CRÉER LA TABLE personnes \ G 2) AFFICHER LES INDEX DES PERSONNES; 3) SÉLECTIONNEZ LE COMPTAGE (1) DEPUIS les personnes;
RolandoMySQLDBA
@RolandoMySQLDBA Je le ferai dès que je me mettrai au travail demain. Cheers :)
RedBlueThing
J'ai mis à jour ma réponse. Lisez s'il vous plaît !!!
RolandoMySQLDBA
@RolandoMySQLDBA Merci :). Analyse toujours ce genre de choses. Je vous ferai savoir comment nous allons.
RedBlueThing

Réponses:

14

Les requêtes UPDATE de vos deux questions précédentes ( Question1 , Question2 ) atteignent la table 'people' de PRIMARY KEY avec verrouillage au niveau de la ligne. C'est ce que j'ai déclaré dans la Question 1 du 6 juin 2011 à 10 h 03.

Toutes les transactions traversent la clé PRIMARY. Étant donné que PRIMARY est un index cluster dans InnoDB, la clé PRIMARY et la ligne elle-même sont ensemble. Ainsi, parcourir une ligne et et la CLÉ PRIMAIRE sont une seule et même chose. Par conséquent, tout verrou d'index sur la CLÉ PRIMAIRE est également un verrou de niveau ligne.

Quelque chose d'autre n'a pas encore été envisagé qui peut attribuer la lenteur aux index: L'utilisation d'index NON-UNIQUE dans InnoDB. Chaque recherche indexée dans InnoDB utilisant des index non uniques a également le rowID de chaque ligne attaché à la clé non unique.Le rowID émane essentiellement de l'index clusterisé . La mise à jour des index non uniques DOIT TOUJOURS interagir avec l'index cluster, MÊME SI LA TABLE N'A PAS DE CLÉ PRIMAIRE.

Une autre chose à penser est le processus de gestion des nœuds BTREE dans un index. Parfois, cela nécessite le fractionnement de page des nœuds. Toutes les entrées du nœud BTREE d'index non uniques contiennent des champs non uniques PLUS le rowID dans l'index cluster. Pour atténuer correctement le fractionnement de ces pages BTREE sans perturber l'intégrité des données, la ligne associée au rowID doit subir un verrouillage de niveau de ligne en interne.

Si la table 'people' a beaucoup d'index non uniques, préparez-vous à avoir un grand nombre de pages d'index dans l'espace de table ainsi que d'avoir de minuscules petits verrous de lignes se faufiler de temps en temps.

Il y a un autre facteur qui n'est pas aussi évident: la population clé

Parfois, lorsqu'un index est rempli, les valeurs de clé constituant les index peuvent devenir déséquilibrées au fil du temps et faire en sorte que MySQL Query Optimizer passe des recherches par clé, aux analyses d'index et enfin aux analyses de table complètes. Que vous ne pouvez pas contrôler à moins que vous ne repensiez la table avec de nouveaux index pour compenser les touches de déséquilibre. Veuillez fournir la structure de la table pour la table «personnes», le nombre de la table «personnes» et la sortie des index pour la table «personnes» .

Même si les requêtes n'utilisent que la PRIMARY KEY, le déséquilibre des clés dans les index non uniques nécessite toujours un équilibrage BTREE et un fractionnement de page. Une telle gestion de BTREE produira un ralentissement notable en raison de verrouillages de niveau de ligne intermittents que vous n'aviez pas l'intention de produire.

MISE À JOUR 2011-06-14 22:19

Requêtes de la question 1

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

Imaginez la séquence des événements

  1. Trouvez la rangée par PRIMARY KEY
  2. Verrouiller la ligne et l'index clusterisé
  3. Créer des données MVCC pour toutes les colonnes en cours de mise à jour
  4. Quatre colonnes sont indexées (email, company_id, iphone_device_id, picture_blob_id)
  5. Chaque indice nécessite la gestion de BTREE
  6. Dans le même espace de transaction, les étapes 1 à 5 tentent de se répéter sur la même ligne, en mettant à jour les mêmes colonnes (envoyer le même dans les deux requêtes, company_id le même dans les deux requêtes, picture_blob_id le même dans les deux requêtes, iphone_device_id différent)

Requêtes de la question 2

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

Ces deux requêtes sont encore plus confuses car la première requête met à jour tout sauf people_id 666. Des centaines de lignes sont douloureusement verrouillées avec uniquement la première requête. La deuxième requête met à jour people_id 666 en exécutant la séquence de 5 événements. La première requête exécute ces mêmes 5 séquences d'événements sur chaque ligne impliquée, sauf people_id 666, mais l'index pour iphone_device_id est sur un cours intercepté avec deux requêtes différentes. Quelqu'un doit verrouiller les pages BTREE sur la base du premier arrivé, premier servi.

Face à ces deux paires de requêtes sur une trajectoire de collision pour éventuellement verrouiller les mêmes pages BTREE dans un index peut être une expérience déchirante pour InnoDB ou tout SGBDR conforme à ACID. Ainsi, un ralentissement d'index est le destin de ces paires de requêtes, sauf si vous pouvez garantir que les requêtes s'exécutent avec AUTOCOMMIT = 1 ou en autorisant des lectures sales (bien que des collisions comme celles-ci font de READ-COMMITTED et READ-UNCOMMITED un cauchemar pour MVCC).

MISE À JOUR 2011-06-15 10:29

@RedBlueThing: Dans les requêtes de la question 2, la première requête est une requête de plage, donc de nombreux verrous de ligne sont atteints. Notez également que les deux requêtes tentent de verrouiller le même espace id 0 page n ° 4611 n bits 152 est verrouillé dans la PRIMARY KEY, alias index cluster.

Afin de vous assurer que votre application fonctionne, à tout le moins, en fonction de la série d'événements que vous attendez, vous pouvez essayer deux options différentes:

Option 1) Convertissez ce tableau en MyISAM (au moins sur un serveur de développement). Chaque mise à jour, insertion et suppression imposera un verrou de table complet sur le principe du premier arrivé, premier servi.

Option 2) Essayez d'utiliser le SERIALIZABLE niveau d'isolement . Cela verrouillera toutes les lignes prévues en mode PARTAGÉ.

La séquence d'événements que vous attendez sera interrompue ou réussie en utilisant ces deux options alternatives. Si ces deux options échouent, vous devrez examiner votre application et prioriser l'ordre d'exécution de vos requêtes. Une fois que vous avez établi cette priorité, vous pouvez simplement annuler ces options (pour l'option 1, revenir à InnoDB, pour l'option 2, revenir au niveau d'isolement par défaut [arrêter d'utiliser SERIALIZABLE]).

RolandoMySQLDBA
la source
@RolandoMySQLDBA J'ai mis à jour notre question avec les détails que vous avez demandés.
RedBlueThing
@RolandoMySQLDBA Merci d'avoir revu cela. Je me demandais, vous commentez la question 2, pourquoi la première requête verrouillerait-elle des centaines de lignes? Ne verrouillerait-il pas uniquement les lignes non 666 qui correspondent à l'ID de l'appareil? (c.-à-d. une seule rangée)
RedBlueThing
@RolandoMySQLDBA Sur la base de votre suggestion de la question 1, nous avons vérifié notre paramètre de validation automatique et confirmé qu'il était activé.
RedBlueThing
@RolandoMySQLDBA Y a-t-il un problème spécifique avec les requêtes de la première question (à part la mise à jour de tous les champs de la ligne). Quelque chose qui expliquerait un temps d'exécution de 13 secondes pour la requête? J'ai l'impression que l'indexation de quatre colonnes n'est pas quelque chose que vous recommanderiez, mais cela entraînerait-il vraiment de si mauvaises performances?
RedBlueThing
@RolandoMySQLDBA +1 et merci pour toutes vos suggestions. Nous n'avons pas fini par changer le niveau d'isolement pour résoudre le problème. Au lieu de cela, nous avons effectué des mises à jour de champ partielles pour la question 2 et optimisé une requête dans le chemin de mise à jour. Voila! plus de blocages. :)
RedBlueThing
3

AFFICHER LES VARIABLES COMME 'innodb%'; - En particulier, si les données et les index n'ont tout simplement pas atteint la taille du pool de tampons, vous pourriez frapper le disque beaucoup plus fort qu'auparavant. I / O est le grand tueur de performance.

La plupart de vos champs sont deux fois plus grands que nécessaire. BIGINT (8 octets) est beaucoup trop pour la plupart des identifiants. 5000 lignes n'ont besoin que d'un SMALLINT UNSIGNED (limite de 65 Ko, seulement 2 octets). Ou utilisez MEDIUMINT pour une marge de sécurité.

DOUBLE vous donne 16 chiffres significatifs pour un coût de 8 octets. Battery_level a-t-il plus de 2 chiffres significatifs de précision? FLOAT prend 4 octets.

Mon point ici est que "plus petit -> plus en cache -> plus rapide".

Veuillez nous montrer les requêtes lentes; au moins certains de ceux qui sont soudainement devenus plus lents. Nous ne pouvons que deviner sans eux. Activez le slowlog et définissez long_query_time = 1; ceux-ci vous aideront à trouver les requêtes les plus lentes.

Comprenez-vous les avantages des indices "composés"?

Rick James
la source