¿Razones para consultas ocasionalmente lentas?

16

Estamos ejecutando MySQL 5.1 en Windows Server 2008 R2.

Últimamente hemos estado haciendo algunos diagnósticos en nuestra base de datos y hemos encontrado algunos artefactos perturbadores que no podemos explicar . Agregamos algo de código para iniciar sesión cuando tuvimos consultas que tomaron mucho tiempo (> 2000 ms). Los resultados fueron sorprendentes (y posiblemente una explicación para nuestros puntos muertos).

Ocasionalmente, las consultas, que normalmente toman muy poco tiempo (<10 ms), demoran de 4 a 13 segundos. Para ser claros, estas son consultas que se ejecutan constantemente (varias veces por segundo) y no sufren estos picos de tiempo de consulta.

Hemos revisado nuestros índices en busca de errores obvios y no hemos tenido mucha suerte.

Actualizar

La mesa de la gente:

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

Índices:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| 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      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

Tenemos ~ 5000 filas en la tabla en el servidor que nos está dando problemas.

RedBlueThing
fuente
1
Hay algo que aún no ha mostrado en las dos preguntas anteriores. Agregue a esta pregunta tres (3) cosas: 1) MOSTRAR CREAR TABLA people \ G 2) MOSTRAR ÍNDICES DE personas; 3) SELECCIONE EL CONTEO (1) DE las personas;
RolandoMySQLDBA
@RolandoMySQLDBA Lo haré tan pronto como empiece a trabajar mañana. Saludos :)
RedBlueThing
Actualicé mi respuesta. Por favor lee !!!
RolandoMySQLDBA
@RolandoMySQLDBA Gracias :). Todavía analizando estas cosas. Te diré cómo vamos.
RedBlueThing

Respuestas:

14

Las consultas de ACTUALIZACIÓN en sus dos preguntas anteriores ( Pregunta1 , Pregunta2 ) están golpeando la tabla 'personas' por PRIMARY KEY con bloqueo de nivel de fila. Esto es lo que dije en la Pregunta 1 el 6 de junio de 2011 10:03 a.m.

Todas las transacciones atraviesan la clave PRIMARIA. Dado que PRIMARY es un índice agrupado en InnoDB, la clave PRIMARY y la fila en sí están juntas. Por lo tanto, atravesar una fila y la CLAVE PRIMARIA son lo mismo. Por lo tanto, cualquier bloqueo de índice en PRIMARY KEY también es un bloqueo de nivel de fila.

Todavía no se ha considerado otra cosa que pueda atribuir lentitud a los índices: el uso de índices NO ÚNICOS en InnoDB. Cada búsqueda indexada en InnoDB que utiliza índices no únicos también tiene el ID de fila de cada fila asociada a la clave no única. El rowID básicamente emana del índice agrupado . La actualización de índices no únicos DEBE SIEMPRE interactuar con el índice agrupado INCLUSO SI LA TABLA NO TIENE UNA CLAVE PRIMARIA.

Otra cosa a tener en cuenta es el proceso de gestión de nodos BTREE en un índice. A veces, requiere la división de la página de los nodos. Todas las entradas en el nodo BTREE de índices no únicos contienen campos no únicos MÁS el ID de fila dentro del índice agrupado. Para mitigar adecuadamente la división de tales páginas BTREE sin alterar la integridad de los datos, la fila asociada con el ID de fila debe experimentar un bloqueo de nivel de fila internamente.

Si la tabla de 'personas' tiene muchos índices no únicos, prepárese para tener una gran cantidad de páginas de índice en el espacio de tablas, así como tener pequeños bloqueos de filas que se le acerquen de vez en cuando.

Hay otro factor que no es tan obvio: la población clave

A veces, cuando se llena un índice, los valores clave que componen los índices pueden desviarse con el tiempo y hacer que MySQL Query Optimizer cambie de búsquedas con clave a escaneos de índice y finalmente a escaneos de tabla completa. Eso no puede controlarlo a menos que rediseñe la tabla con nuevos índices para compensar la desigualdad de las teclas. Proporcione la estructura de la tabla para la tabla 'personas', el recuento de la tabla 'personas' y la salida de los índices de la tabla 'personas' .

Incluso si las consultas usan solo la CLAVE PRIMARIA, la desigualdad de las claves en los índices no únicos todavía necesita el equilibrio BTREE y la división de la página para que ocurra. Tal gestión de BTREE producirá una notable desaceleración debido a bloqueos intermitentes de nivel de fila que no tenía la intención de suceder.

ACTUALIZACIÓN 2011-06-14 22:19

Consultas de la pregunta 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

Imagen de la secuencia en eventos

  1. Encuentra la fila por PRIMARY KEY
  2. Bloquee la fila y el índice agrupado
  3. Crear datos MVCC para todas las columnas que se actualizan
  4. Se indexan cuatro columnas (email, company_id, iphone_device_id, picture_blob_id)
  5. Cada índice requiere gestión BTREE
  6. Dentro del mismo espacio de transacción, los pasos 1-5 intentan repetirse en la misma fila, actualizando las mismas columnas (enviar el mismo correo electrónico en ambas consultas, company_id lo mismo en ambas consultas, picture_blob_id lo mismo en ambas consultas, iphone_device_id diferente)

Consultas de la pregunta 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;

Estas dos consultas son aún más confusas porque la primera consulta está actualizando todo excepto people_id 666. Cientos de filas se están bloqueando dolorosamente con solo la primera consulta. La segunda consulta está actualizando people_id 666 ejecutando la secuencia de 5 eventos. La primera consulta ejecuta esas mismas 5 secuencias de eventos en cada fila involucrada, excepto people_id 666, pero el índice para iphone_device_id está en un curso de intersección con dos consultas diferentes. Alguien tiene que bloquear las páginas de BTREE por orden de llegada.

Frente a estos dos pares de consultas en un curso de colisión para posiblemente bloquear las mismas páginas BTREE dentro de un índice, puede ser una experiencia desgarradora para InnoDB o cualquier RDBMS compatible con ACID. Por lo tanto, una ralentización del índice es el destino de estos pares de consultas a menos que pueda garantizar que las consultas se ejecuten con AUTOCOMMIT = 1 o permitiendo lecturas sucias (aunque colisiones como estas hacen que READ-COMPROMISO y READ-NO COMPROMISO sea una pesadilla para MVCC).

ACTUALIZACIÓN 2011-06-15 10:29

@RedBlueThing: en las consultas de la pregunta 2, la primera consulta es una consulta de rango, por lo que se están logrando muchos bloqueos de fila. Observe también que ambas consultas están tratando de bloquear el mismo espacio id 0 página no 4611 n bits 152 se está bloqueando en la PRIMARY KEY, también conocido como índice agrupado.

Para asegurarse de que su aplicación, como mínimo, se ejecute según la serie de eventos que espera, hay dos opciones diferentes que puede probar:

Opción 1) Convertir esta tabla a MyISAM (al menos en un servidor de desarrollo). Cada ACTUALIZACIÓN, INSERTAR y ELIMINAR impondrá un bloqueo de tabla completo por orden de llegada.

Opción 2) Intente usar el nivel de aislamiento SERIALIZABLE . Eso bloqueará todas las filas previstas en el modo COMPARTIDO.

La secuencia de eventos que espera se interrumpirá o tendrá éxito utilizando estas dos opciones alternativas. Si ambas opciones fallan, deberá revisar su aplicación y priorizar el orden de ejecución de sus consultas. Una vez que establezca esa prioridad, simplemente puede deshacer estas opciones (para la opción 1, regrese a InnoDB, para la opción 2, regrese al nivel de aislamiento predeterminado [deje de usar SERIALIZABLE]).

RolandoMySQLDBA
fuente
@RolandoMySQLDBA Actualicé nuestra pregunta con los detalles que solicitó.
RedBlueThing
@RolandoMySQLDBA Gracias por echar un vistazo a esto. Me preguntaba, usted comenta para la pregunta 2, ¿por qué la primera consulta bloquearía cientos de filas? ¿No bloquearía solo las filas no 666 que coinciden con la identificación del dispositivo? (es decir, una sola fila)
RedBlueThing
@RolandoMySQLDBA Según su sugerencia de la Pregunta 1, verificamos nuestra configuración de confirmación automática y confirmamos que está activada.
RedBlueThing
@RolandoMySQLDBA ¿Hay un problema específico con las consultas de la primera pregunta (aparte de actualizar todos los campos de la fila)? ¿Algo que explicaría un tiempo de ejecución de 13 segundos para la consulta? Tengo la sensación de que indexar cuatro columnas no es algo que recomendaría, pero ¿esto realmente resultaría en un rendimiento tan pobre?
RedBlueThing
@RolandoMySQLDBA +1 y gracias por todas sus sugerencias. No terminamos cambiando el nivel de aislamiento para resolver el problema. En cambio, realizamos actualizaciones parciales de campo para la pregunta 2 y optimizamos una consulta en la ruta de actualización. Voila! No más puntos muertos. :)
RedBlueThing
3

MOSTRAR VARIABLES COMO 'innodb%'; - En particular, si los datos e índices simplemente no han alcanzado el tamaño del grupo de búferes, podría estar golpeando el disco mucho más fuerte que antes. I / O es el gran asesino de rendimiento.

La mayoría de sus campos son dos veces más grandes de lo necesario. BIGINT (8 bytes) es una exageración para la mayoría de los identificadores. 5000 filas solo necesitan un SMALLINT UNSIGNED (límite de 65K, solo 2 bytes). O use MEDIUMINT por un margen de seguridad.

DOBLE le da 16 dígitos significativos a un costo de 8 bytes. ¿Battery_level tiene más de 2 dígitos significativos de precisión? FLOAT toma 4 bytes.

Mi punto aquí es que "más pequeño -> más almacenable en caché -> más rápido".

Por favor, muéstranos las consultas lentas; al menos algunos de los que de repente se han vuelto más lentos. Solo podemos hacer conjeturas sin ellos. Active el registro lento y establezca long_query_time = 1; Esto ayudará a encontrar las consultas más lentas.

¿Entiende el beneficio de los índices "compuestos"?

Rick James
fuente