Motivos para consultas lentas ocasionalmente?

16

Estamos executando o MySQL 5.1 no Windows Server 2008 R2.

Ultimamente, temos feito alguns diagnósticos em nosso banco de dados e encontramos alguns artefatos perturbadores que não podemos explicar . Adicionamos algum código ao log quando tivemos consultas que demoraram muito (> 2000 ms). Os resultados foram surpreendentes (e possivelmente uma explicação para nossos impasses).

Ocasionalmente, consultas, que normalmente levam muito pouco tempo (<10 ms), levam de 4 a 13 segundos. Para ser claro, essas são consultas que estão sendo executadas constantemente (várias vezes por segundo) e que não sofrem com esses picos de tempo de consulta.

Analisamos nossos índices à procura de erros óbvios e não tivemos muita sorte.

Atualizar

A tabela de pessoas:

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

temos ~ 5000 linhas na tabela no servidor que está nos causando problemas.

RedBlueThing
fonte
11
Há algo que você ainda não mostrou nas duas perguntas anteriores. Adicione a esta pergunta três (3) coisas: 1) MOSTRAR CRIAR TABELA pessoas \ G 2) MOSTRAR ÍNDICES DE pessoas; 3) SELECIONE A CONTAGEM (1) DE pessoas;
RolandoMySQLDBA
@RolandoMySQLDBA Farei isso assim que entrar no trabalho amanhã. Cheers :)
RedBlueThing
Eu atualizei minha resposta. Por favor leia !!!
RolandoMySQLDBA 15/06
@RolandoMySQLDBA Thanks :). Ainda estou analisando essas coisas. Eu vou deixar você saber como vamos.
RedBlueThing

Respostas:

14

O UPDATE consulta em suas duas perguntas anteriores ( Pergunta1 , Pergunta2 ) estão atingindo a tabela 'pessoas' por PRIMARY KEY com bloqueio no nível da linha. Foi o que afirmei na Pergunta 1 em 6 de junho de 2011 10:03

Todas as transações estão atravessando a chave PRIMARY. Como o PRIMARY é um índice clusterizado no InnoDB, a chave PRIMARY e a própria linha estão juntas. Assim, atravessar uma linha ee a PRIMARY KEY são uma e a mesma. Portanto, qualquer bloqueio de índice na PRIMARY KEY também é um bloqueio de nível de linha.

Ainda não foi considerado algo que possa atribuir lentidão aos índices: O uso de índices NÃO-UNIQUE no InnoDB. Toda pesquisa indexada no InnoDB usando índices não exclusivos também possui o ID da linha de cada linha anexada à chave não exclusiva. O rowID basicamente emina a partir do Clustered Index . A atualização de índices não exclusivos DEVE SEMPRE interagir com o índice clusterizado, MESMO SE A TABELA NÃO TEM UMA CHAVE PRIMÁRIA.

Outra coisa a se pensar é o processo de gerenciamento de nós BTREE em um índice. Às vezes, requer a divisão da página dos nós. Todas as entradas no nó BTREE de índices não exclusivos contêm campos não exclusivos, MAIS o rowID dentro do índice em cluster. Para mitigar adequadamente a divisão dessas páginas BTREE sem perturbar a integridade dos dados, a linha associada ao ID da linha deve experimentar um bloqueio de nível de linha internamente.

Se a tabela 'pessoas' tiver muitos índices não exclusivos, prepare-se para ter um grande número de páginas de índice no espaço de tabela, além de ter pequenas e minúsculas linhas de bloqueio ocultando você periodicamente.

Existe outro fator que não é tão óbvio: população-chave

Às vezes, quando um índice é preenchido, os valores-chave que compõem os índices podem ficar desequilibrados ao longo do tempo e fazer com que o MySQL Query Optimizer mude de pesquisas com chave, para verificações de índice e, finalmente, para verificações de tabela completa. Isso você não pode controlar, a menos que redesenhe a tabela com novos índices para compensar a desatenção das teclas. Por favor, forneça a estrutura da tabela para a tabela 'people', a contagem da tabela 'people' e a saída dos índices show para a tabela 'people' .

Mesmo que as consultas usem apenas a PRIMARY KEY, a desigualdade de chaves em índices não exclusivos ainda precisará do equilíbrio BTREE e da divisão da página. Esse gerenciamento do BTREE produzirá uma desaceleração notável devido a bloqueios intermitentes no nível da linha que você não pretendia que acontecesse.

ATUALIZAÇÃO 14/06/2011 22:19

Consultas da pergunta 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

Imagine a sequência em eventos

  1. Encontre a linha por PRIMARY KEY
  2. Bloquear a linha e o índice em cluster
  3. Criar dados MVCC para todas as colunas que estão sendo atualizadas
  4. Quatro colunas são indexadas (email, company_id, iphone_device_id, picture_blob_id)
  5. Cada índice requer gerenciamento BTREE
  6. No mesmo espaço de transação, as etapas 1 a 5 tentam ser repetidas na mesma linha, atualizando as mesmas colunas (enviar o mesmo por email em ambas as consultas, company_id o mesmo em ambas as consultas, picture_blob_id o mesmo em ambas as consultas, iphone_device_id diferente)

Consultas da pergunta 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;

Essas duas consultas são ainda mais confusas porque a primeira consulta está atualizando tudo, exceto people_id 666. Centenas de linhas estão sendo dolorosamente bloqueadas apenas com a primeira consulta. A segunda consulta está atualizando people_id 666 executando a sequência 5 de eventos. A primeira consulta está executando as mesmas 5 sequências de eventos em todas as linhas envolvidas, exceto people_id 666, mas o índice para iphone_device_id está em um curso de interecepção com duas consultas diferentes. Alguém precisa bloquear as páginas do BTREE por ordem de chegada.

Diante desses dois pares de consultas em rota de colisão, para possivelmente bloquear as mesmas páginas BTREE em um índice, pode ser uma experiência instigante para o InnoDB ou qualquer RDBMS compatível com ACID. Portanto, uma desaceleração do índice é o destino desses pares de consultas, a menos que você possa garantir que as consultas sejam executadas com AUTOCOMMIT = 1 ou permitindo leituras sujas (embora colisões como essas tornem READ-COMMITTED e READ-UNCOMMITED um pesadelo para o MVCC).

ATUALIZAÇÃO 15/06/2011 10:29

@RedBlueThing: nas consultas da pergunta 2, a primeira consulta é uma consulta de intervalo, portanto, muitos bloqueios de linha estão sendo atingidos. Observe também que ambas as consultas estão tentando bloquear o mesmo ID de espaço 0, página 4611 n bits 152 estão sendo bloqueados na PRIMARY KEY, também conhecida como índice clusterizado.

Para garantir que seu aplicativo seja executado, no mínimo, com base na série de eventos que você espera, existem duas opções diferentes que você pode tentar:

Opção 1) Converta esta tabela para MyISAM (pelo menos em um servidor de desenvolvimento). Cada UPDATE, INSERT e DELETE imporá um bloqueio de tabela completo por ordem de chegada.

Opção 2) Tente usar o nível de isolamento SERIALIZABLE . Isso bloqueará todas as linhas pretendidas no modo COMPARTILHADO.

A sequência de eventos que você espera será interrompida ou terá êxito usando essas duas opções alternativas. Se ambas as opções falharem, será necessário examinar seu aplicativo e priorizar a ordem de execução das suas consultas. Depois de estabelecer essa prioridade, você pode simplesmente desfazer essas opções (na opção 1, volte ao InnoDB, na opção 2, volte ao nível de isolamento padrão [pare de usar SERIALIZABLE]).

RolandoMySQLDBA
fonte
@RolandoMySQLDBA Atualizei nossa pergunta com os detalhes solicitados.
RedBlueThing
@RolandoMySQLDBA Obrigado por dar uma nova olhada nisso. Eu queria saber, você comenta a questão 2, por que a primeira consulta trava centenas de linhas? Não bloquearia apenas 666 linhas que correspondem ao ID do dispositivo? (ou seja, uma única linha)
RedBlueThing
@RolandoMySQLDBA Com base na sua sugestão da pergunta 1, verificamos nossa configuração de confirmação automática e confirmamos que ela está ativada.
RedBlueThing
@RolandoMySQLDBA Existe um problema específico com as consultas da primeira pergunta (além de atualizar todos os campos da linha). Algo que explicaria um tempo de execução de 13 segundos para a consulta? Tenho a sensação de que a indexação de quatro colunas não é algo que você recomendaria, mas isso realmente resultaria em um desempenho tão ruim?
RedBlueThing
@RolandoMySQLDBA +1 e obrigado por todas as suas sugestões. Não acabamos alterando o nível de isolamento para resolver o problema. Em vez disso, fizemos atualizações parciais de campo para a pergunta 2 e otimizamos uma consulta no caminho da atualização. Voila! sem mais impasses. :)
RedBlueThing
3

MOSTRAR VARIÁVEIS COMO 'innodb%'; - Em particular, se os dados e índices simplesmente não atingirem o tamanho do buffer pool, você poderá estar atingindo o disco com muito mais força do que antes. A E / S é a grande causa de desempenho.

A maioria dos seus campos é duas vezes maior que o necessário. BIGINT (8 bytes) é um exagero para a maioria dos IDs. 5000 linhas precisam apenas de um SMALLINT UNSIGNED (limite de 65K, apenas 2 bytes). Ou use MEDIUMINT para obter uma margem de segurança.

DOUBLE fornece 16 dígitos significativos a um custo de 8 bytes. O nível da bateria possui mais de 2 dígitos significativos de precisão? FLUTUAR leva 4 bytes.

O que quero dizer aqui é que "menor -> mais armazenável em cache -> mais rápido".

Por favor, mostre-nos as consultas lentas; pelo menos alguns dos que se tornaram subitamente mais lentos. Só podemos fazer suposições sem eles. Ligue o slowlog e defina long_query_time = 1; isso ajudará a encontrar as consultas mais lentas.

Você entende o benefício dos índices "compostos"?

Rick James
fonte