Percorrer uma tabela MyISAM de forma controlada

August 26th, 2009 ntavares Posted in mysql, pt_PT No Comments »

Ouvir com webReader

Lembrei-me de partilhar um teste que fiz há tempos. O que se pretendia era demonstrar que podemos percorrer uma tabela MyISAM (e só neste caso específico) de forma controlada (ie, prever a ordem pela qual os registos são devolvidos) e se esse percurso se mantém em outras operações para além do SELECT, como no caso do DELETE. É fácil pensar numa aplicação: se eu quiser, por exemplo, transladar blocos de registos de uma tabela para outra, torna-se fundamental que a operação DELETE também obedeça ao expectável já que, como sabemos, o MyISAM não é transaccional e, se alguma coisa falhar, queremos poder ter acesso aos registos desta forma determinística para saber o que reverter.

Para alguns pode parecer óbvio, mas sem olhar para o código nunca vamos ter a certeza. Para além disso, o código pode mudar, por isso mais vale termos a certeza :-) Não vamos sequer tentar extrapolar as conclusões para InnoDB porque internamente trabalha de forma completamente diferente. Aliás, um único aspecto da sua arquitectura - o famoso clustered index, que pode ou não ser interno, mas existe sempre! - dá logo para desconfiar que o comportamento seja completamente diferente.

Portanto, na prática, o que se pretende mesmo é ter a certeza que sabemos que registos vão surgindo em várias iterações, e se essa certeza se extrapola para DELETEs (e, eventualmente, para UPDATEs) - ie, tornar o nosso processo determinístico.

Vamos começar com uma tabela simples:

MySQL:
  1. CREATE TABLE `teste` (
  2.   `_int` INT(11) NOT NULL DEFAULT '0',
  3.   `_char` VARCHAR(5) NOT NULL DEFAULT '',
  4.   KEY `idx_int` (`_int`)
  5. ) ENGINE=MyISAM DEFAULT CHARSET=latin1

E inserimos alguns dados:

MySQL:
  1. mysql> INSERT INTO teste VALUES (2,'c'), (1,'e'), (1,'b'), (1,'z'), (2,'b'), (2,'d'),(3,'m');
  2. Query OK, 6 rows affected (0.00 sec)
  3. Records: 6  Duplicates: 0  WARNINGS: 0
  4.  
  5. mysql> SELECT SQL_NO_CACHE * FROM teste;
  6. +------+-------+
  7. | _int | _char |
  8. +------+-------+
  9. |    2 | c     |
  10. |    1 | e     |
  11. |    1 | b     |
  12. |    1 | z     |
  13. |    2 | b     |
  14. |    2 | d     |
  15. |    3 | m     |
  16. +------+-------+
  17. 7 rows in SET (0.00 sec)

A ordem pela qual foram inseridos os registos é fundamental. Podemos observar que este table scan é feito de forma natural, também segundo o Query Optimizer:

MySQL:
  1. mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM teste;
  2. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  3. | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
  4. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  5. 1 | SIMPLE      | teste | ALL  | NULL          | NULL | NULL    | NULL |    7 |       |
  6. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  7. 1 row in SET (0.00 sec)

Perfeito. Estou a pedir os campos todos de cada linha, onde se inclui _char, que não é indexado, e o Optimizer comporta-se como suposto. Mas agora vejamos uma query ligeiramente diferente:

MySQL:
  1. mysql> EXPLAIN SELECT SQL_NO_CACHE _int FROM teste;
  2. +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
  3. | id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
  4. +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
  5. 1 | SIMPLE      | teste | index | NULL          | idx_int | 4       | NULL |    7 | USING index |
  6. +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
  7. 1 row in SET (0.00 sec)

É interessante como o Optimizer reconheceu que, se eu só quero um campo que por acaso até está indexado, então posso obtê-lo directamente do índice (e é isso que significa Using index no campo Extra) evitando ir aos datafiles. Porém isto significa que iremos obter os registos pela ordem do índice:

MySQL:
  1. mysql> SELECT SQL_NO_CACHE _int FROM teste;
  2. +------+
  3. | _int |
  4. +------+
  5. |    1 |
  6. |    1 |
  7. |    1 |
  8. |    2 |
  9. |    2 |
  10. |    2 |
  11. |    3 |
  12. +------+
  13. 7 rows in SET (0.00 sec)

Isto é mais importante que o que possa parecer para este teste. Se eu for obrigado a requisitar mais campos do que esse, o Optimizer vai voltar ao table scan. E não basta colocar um ORDER BY...

MySQL:
  1. mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM teste ORDER BY _int;
  2. +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
  3. | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra          |
  4. +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
  5. 1 | SIMPLE      | teste | ALL  | NULL          | NULL | NULL    | NULL |    7 | USING filesort |
  6. +----+-------------+-------+------+---------------+------+---------+------+------+----------------+
  7. 1 row in SET (0.00 sec)

... porque o Optimizer pode querer usar uma tabela temporária para fazer a ordenação, que é que nos diz Using filesort no campo Extra. Isto pode parecer uma falha do Optimizer, mas a verdade é que o Optimizer é inteligente o suficiente para determinar que um ''full table scan'' pode ser mais eficiente que percorrer o índice por uma ordem e ir buscar os restantes dados aos data files com localização (nos discos) completamente desordenada, provavelmente aleatória (o I/O manifestar-se-ia imediatamente) - claro que nesta tabela talvez não seja o caso, mas para tabelas muito grandes pode ser desastroso. Assim sendo, teríamos que forçar explicitamente a utilização do índice, já que, pelo menos no meu caso, nem a pista USE INDEX ajudava:

MySQL:
  1. mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM teste FORCE INDEX(idx_int) ORDER BY _int;
  2. +----+-------------+-------+-------+---------------+---------+---------+------+------+-------+
  3. | id | select_type | table | type  | possible_keys | key     | key_len | ref  | rows | Extra |
  4. +----+-------------+-------+-------+---------------+---------+---------+------+------+-------+
  5. 1 | SIMPLE      | teste | index | NULL          | idx_int | 4       | NULL |    7 |       |
  6. +----+-------------+-------+-------+---------------+---------+---------+------+------+-------+
  7. 1 row in SET (0.00 sec)

De facto, o Optimizer é tão teimoso que mesmo forçando a utilização do índice ele descarta-o se não usarmos o ORDER BY, pois sabe que, para um table scan a ordem dos registos é indiferente e, como tal, não precisa do índice para nada. Deve haver uma explicação para este comportamento - que vou ter que pesquisar - mas este comportamento interessa-nos e muito: se o Optimizer pegasse ao acaso um índice que lhe parecesse bem, seria difícil obter os registos pela ordem natural sem testar com um EXPLAIN primeiro. Parece-me interessante salientar o seguinte:

MySQL:
  1. mysql> SELECT SQL_NO_CACHE * FROM teste FORCE INDEX(idx_int) ORDER BY _int;
  2. +------+-------+
  3. | _int | _char |
  4. +------+-------+
  5. |    1 | e     |
  6. |    1 | b     |
  7. |    1 | z     |
  8. |    2 | c     |
  9. |    2 | b     |
  10. |    2 | d     |
  11. |    3 | m     |
  12. +------+-------+
  13. 7 rows in SET (0.00 sec)

Ou seja, dentro do índice, quando há colisões, elas são simplesmente adicionadas no fim. Isto significa que, após a ordenação, a ordem pela qual obtemos os registos é... a ordem natural.

Mas pronto, agora sim, podemos assumir, para já, que se percorrermos a tabela com SELECT ... LIMIT 1, podemos ir obtendo registo a registo quer pela ordem natural, quer pela ordem do índice que quisermos. Mas o meu grande problema era na remoção, pois não temos EXPLAIN para o DELETE. Qual dos dois métodos o DELETE utiliza?

MySQL:
  1. mysql> DELETE FROM teste LIMIT 1;
  2. Query OK, 1 row affected (0.00 sec)
  3.  
  4. mysql> SELECT SQL_NO_CACHE * FROM teste;
  5. +------+-------+
  6. | _int | _char |
  7. +------+-------+
  8. |    1 | e     |
  9. |    1 | b     |
  10. |    1 | z     |
  11. |    2 | b     |
  12. |    2 | d     |
  13. |    3 | m     |
  14. +------+-------+
  15. 6 rows in SET (0.00 sec)

Bem, para já, parece ser a ordem natural. Claro que se eu especificar um ORDER BY _int o próximo registo a apagar deveria ser (1,e) - porque é o primeiro no índice idx_int, e sabemos nós que o valor no campo _char será o da ordem natural - resta saber se o Optimizer não pensa que precisa duma tabela temporária para essa ordenação. Eu estou convencido que não, pois como não há selecção de nenhum campo específico, não há porque não utilizar o índice idx_int;. Vamos só confirmar:

MySQL:
  1. mysql> DELETE FROM teste ORDER BY _int LIMIT 1;
  2. Query OK, 1 row affected (0.00 sec)
  3.  
  4. mysql> SELECT SQL_NO_CACHE * FROM teste;
  5. +------+-------+
  6. | _int | _char |
  7. +------+-------+
  8. |    1 | b     |
  9. |    1 | z     |
  10. |    2 | b     |
  11. |    2 | d     |
  12. |    3 | m     |
  13. +------+-------+
  14. 5 rows in SET (0.00 sec)

Tudo bem, conforme previsto. Mas há mais. Raramente há tabelas só com um índice e pode acontecer que o nosso campo _char fosse apanhado por um índice, o que tornaria as coisas um pouco diferentes:

MySQL:
  1. mysql> ALTER TABLE teste ADD KEY idx_char(_char);
  2. Query OK, 5 rows affected (0.00 sec)
  3. Records: 5  Duplicates: 0  WARNINGS: 0
  4.  
  5. mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM teste;
  6. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  7. | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
  8. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  9. 1 | SIMPLE      | teste | ALL  | NULL          | NULL | NULL    | NULL |    5 |       |
  10. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  11. 1 row in SET (0.00 sec)

Acho interessante porque é como se o Optimizer «em caso de dúvida, optasse por nenhum», ou seja, como desconhece um critério para escolher um ou outro índice, não usa nenhum.

Na verdade, o Optimizer não usa nenhum índice porque não tem nenhuma pista sobre qual agarrar. Por norma irá utilizar o de maior cardinalidade, para as pistas que tiver disponíveis:

MySQL:
  1. mysql> OPTIMIZE TABLE teste;
  2. +------------+----------+----------+----------+
  3. | Table   | Op      | Msg_type | Msg_text
  4. +------------+----------+----------+----------+
  5. | test.teste | OPTIMIZE | status   | OK    
  6. +------------+----------+----------+----------+
  7. 1 row in SET (0.00 sec)
  8.  
  9. mysql> SHOW INDEXES FROM teste;
  10. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
  11. | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | NULL | Index_type | Comment
  12. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
  13. | teste |        1 | idx_int  |         1 | _int      | A         |       2 |     NULL | NULL   |      | BTREE     |        
  14. | teste |        1 | idx_char |         1 | _char     | A         |       5 |     NULL | NULL   |      | BTREE     |        
  15. +-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
  16. 2 rows in SET (0.00 sec)
  17.  
  18. mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM teste WHERE _int = 1 AND _char = 'e';
  19. +----+-------------+-------+------+------------------+----------+---------+-------+------+-------------+
  20. | id | select_type | table | type | possible_keys   | key    | key_len | ref   | rows | Extra      
  21. +----+-------------+-------+------+------------------+----------+---------+-------+------+-------------+
  22. 1 | SIMPLE     | teste | ref  | idx_int,idx_char | idx_char | 7    | const | 1 | USING WHERE
  23. +----+-------------+-------+------+------------------+----------+---------+-------+------+-------------+
  24. 1 row in SET (0.00 sec)

Ou seja, idx_char é utilizado para a filtragem e, como tinha potencial para filtrar mais registos, é esse índice o escolhido pelo Optimizer, que nos diz ainda que vai precisar de percorrer os datafiles para filtrar o campo idx_int (Using where).

Eu sei que o * é expandido para os campos todos pelo Optimizer; então e se houvesse um covering index?

MySQL:
  1. mysql> ALTER TABLE teste ADD KEY idx_int_char(_int,_char);
  2. Query OK, 5 rows affected (0.01 sec)
  3. Records: 5  Duplicates: 0  WARNINGS: 0
  4.  
  5. mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM teste;
  6. +----+-------------+-------+-------+---------------+--------------+---------+------+------+-------------+
  7. | id | select_type | table | type  | possible_keys | key          | key_len | ref  | rows | Extra      
  8. +----+-------------+-------+-------+---------------+--------------+---------+------+------+-------------+
  9. 1 | SIMPLE     | teste | index | NULL        | idx_int_char | 11    | NULL |    5 | USING index
  10. +----+-------------+-------+-------+---------------+--------------+---------+------+------+-------------+
  11. 1 row in SET (0.00 sec)

Como era de esperar, o índice será usado. Vamos ver o que acontece com o DELETE. Para testar, forçamos os registos a mudar a posição no índice (mantendo a ordem natural) e vamos criar outra tabela similar, com os mesmos registos, porque vai-nos interessar esta ordem mais abaixo:

MySQL:
  1. mysql> UPDATE teste SET _int = 4 WHERE _int = 1 AND _char = 'b'; UPDATE teste SET _int = 5, _char = 'f' WHERE _int = 2 AND _char = 'b';
  2.  
  3. mysql> CREATE TABLE teste3 like teste;
  4. Query OK, 0 rows affected (0.00 sec)
  5.  
  6. mysql> INSERT INTO teste3 SELECT * FROM teste IGNORE INDEX(idx_int_char);
  7. Query OK, 5 rows affected (0.00 sec)
  8. Records: 5  Duplicates: 0  WARNINGS: 0
  9.  
  10. mysql> SELECT * FROM teste3 IGNORE INDEX(idx_int_char);
  11. +------+-------+
  12. | _int | _char |
  13. +------+-------+
  14. |    4 | b     |
  15. |    1 | z     |
  16. |    5 | f     |
  17. |    2 | d     |
  18. |    3 | m     |
  19. +------+-------+
  20. 5 rows in SET (0.00 sec)
  21.  
  22. mysql> DELETE FROM teste3 LIMIT 1;
  23. Query OK, 1 row affected (0.00 sec)
  24.  
  25. mysql> SELECT * FROM teste3 IGNORE INDEX(idx_int_char);
  26. +------+-------+
  27. | _int | _char |
  28. +------+-------+
  29. |    1 | z     |
  30. |    5 | f     |
  31. |    2 | d     |
  32. |    3 | m     |
  33. +------+-------+
  34. 4 rows in SET (0.00 sec)

Ou seja, com o SELECT antes e depois temos oportunidade de comprovar que o DELETE não pegou nenhum índice, nem mesmo o covering index.

Mas voltando atrás, à tabela teste, será que o facto de se usar uma PRIMARY KEY (que é única e não nula) influencia os resultados? Esta pergunta provavelmente só será pertinente para quem conheça o InnoDB.

MySQL:
  1. mysql> ALTER TABLE teste DROP KEY idx_int;
  2. Query OK, 5 rows affected (0.01 sec)
  3. Records: 5  Duplicates: 0  WARNINGS: 0
  4.  
  5. mysql> ALTER TABLE teste ADD PRIMARY KEY(_int);
  6. Query OK, 5 rows affected (0.01 sec)
  7. Records: 5  Duplicates: 0  WARNINGS: 0
  8.  
  9. mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM teste;
  10. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  11. | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |
  12. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  13. 1 | SIMPLE      | teste | ALL  | NULL          | NULL | NULL    | NULL |    5 |       |
  14. +----+-------------+-------+------+---------------+------+---------+------+------+-------+
  15. 1 row in SET (0.00 sec)

Muito bem, nada a assinalar.

Mas nós também sabemos o que acontece com o MyISAM: após os DELETEs começam a surgir buracos nos data files (que, consequentemente, acabam por interferir com as inserções em concorrência). Vejamos. Recriei a tabela teste com o dataset original, e fiz os dois DELETE que fizémos até aqui. A seguir:

MySQL:
  1. mysql> SELECT @@concurrent_insert;
  2. +---------------------+
  3. | @@concurrent_insert
  4. +---------------------+
  5. |               1
  6. +---------------------+
  7. 1 row in SET (0.00 sec)
  8.  
  9. mysql> INSERT INTO teste VALUES (6,'e'),(5,'f'),(4,'g');
  10. Query OK, 3 rows affected (0.00 sec)
  11. Records: 3  Duplicates: 0  WARNINGS: 0
  12.  
  13. mysql> SELECT SQL_NO_CACHE * FROM teste;
  14. +------+-------+
  15. | _int | _char |
  16. +------+-------+
  17. |   5 | f     |
  18. |   6 | e     |
  19. |   1 | b     |
  20. |   1 | z     |
  21. |   2 | b     |
  22. |   2 | d     |
  23. |   3 | m     |
  24. |   4 | g     |
  25. +------+-------+
  26. 8 rows in SET (0.00 sec)

Repare-se que os dois primeiros registos ficaram fora da ordem natural pois o MyISAM conseguiu reciclar o espaço livre, exactamente nos mesmos sítios. O terceiro elemento calhou no único sítio onde cabia: no fim. Esta também é uma observação importante porque se houverem escritas na tabela durante as operações, então é preciso um cuidado especial. Este cuidado agrava-se pelo facto de nem conseguirmos desactivar este comportamento. Recrie-se novamente a tabela teste, com o INSERT inicial, depois os dois DELETEs feitos até agora, e depois o INSERT do teste anterior:

MySQL:
  1. mysql> SET global concurrent_insert = 0;
  2. Query OK, 0 rows affected (0.00 sec)
  3.  
  4. mysql> INSERT INTO teste VALUES (6,'e'),(5,'f'),(4,'g');
  5. Query OK, 3 rows affected (0.00 sec)
  6. Records: 3  Duplicates: 0  WARNINGS: 0
  7.  
  8. mysql> SELECT SQL_NO_CACHE * FROM teste;
  9. +------+-------+
  10. | _int | _char |
  11. +------+-------+
  12. |   5 | f     |
  13. |   6 | e     |
  14. |   1 | b     |
  15. |   1 | z     |
  16. |   2 | b     |
  17. |   2 | d     |
  18. |   3 | m     |
  19. |   4 | g     |
  20. +------+-------+
  21. 8 rows in SET (0.00 sec)

Exactamente o mesmo. Perde-se, portanto, a ordem natural.

Nota sobre o InnoDB

A título de nota, deixa-se uma breve explicação do porquê de não analisarmos o caso do InnoDB:

MySQL:
  1. mysql> CREATE TABLE teste2 like teste;
  2. Query OK, 0 rows affected (0.00 sec)
  3.  
  4. mysql> INSERT INTO teste2 SELECT * FROM teste;
  5. Query OK, 5 rows affected (0.01 sec)
  6. Records: 5  Duplicates: 0  WARNINGS: 0
  7.  
  8. mysql> UPDATE teste2 SET _char = 'k' WHERE _int = 1 and _char = 'b';
  9. Query OK, 1 row affected (0.03 sec)
  10. Rows matched: 1  Changed: 1  WARNINGS: 0
  11.  
  12. mysql> ALTER TABLE teste2 engine=INNODB, DROP INDEX idx_int, DROP INDEX idx_char, DROP INDEX idx_int_char;
  13. Query OK, 5 rows affected (0.01 sec)
  14. Records: 5  Duplicates: 0  WARNINGS: 0
  15.  
  16. mysql> ALTER TABLE teste2 add PRIMARY KEY (_char);
  17. Query OK, 5 rows affected (0.02 sec)
  18. Records: 5  Duplicates: 0  WARNINGS: 0
  19.  
  20. mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM teste2;
  21. +----+-------------+--------+------+---------------+------+---------+------+------+-------+
  22. | id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows | Extra
  23. +----+-------------+--------+------+---------------+------+---------+------+------+-------+
  24. 1 | SIMPLE     | teste2 | ALL  | NULL        | NULL | NULL    | NULL |    5 |    
  25. +----+-------------+--------+------+---------------+------+---------+------+------+-------+
  26. 1 row in SET (0.00 sec)
  27.  
  28. mysql> SELECT * FROM teste2;
  29. +------+-------+
  30. | _int | _char |
  31. +------+-------+
  32. |   2 | b     |
  33. |   2 | d     |
  34. |   1 | k     |
  35. |   3 | m     |
  36. |   1 | z     |
  37. +------+-------+
  38. 5 rows in SET (0.00 sec)

O que queria que reparassem é que não há indicação de estar a ser utilizada nenhuma chave e, no entanto, os registos vêm ordenados pela PRIMARY KEY. Como disse logo no início, com InnoDB os resultados seriam diferentes, e isto deve-se ao clustered index deste storage engine, que armazena os registos (fiscamente nos datafiles) pela ordem da chave (a famosa primary key order). Como consequência, não é possível ter acesso aos registos na forma natural (ie, pela ordem em que foram inseridos) - e, de forma consistente com o que tínhamos visto para MyISAM, o Optimizer prefere não usar nenhum índice, passando para o storage engine a mesma query expandida.

Penso que consegui demonstrar com alguma segurança que podemos obter registos com SELECT de uma forma controlada. Dependendo do cenário, nem sempre se torna possível usar um ou outro método (também pode não ser possível criar um novo índice, por exemplo) pelo que antes de tirar conclusões precipitadas, o melhor é usar o EXPLAIN para explorar as possibilidades!

AddThis Social Bookmark Button

VARCHAR index size in InnoDB

August 22nd, 2009 ntavares Posted in en_US, mysql, performance No Comments »

Ouvir com webReader

Although my previous conclusions about VARCHAR influence on index size could be quite storage engine specific, I'd like to see if we can extend them to InnoDB, so I took the tables still lying on my disk and did:

MySQL:
  1. mysql> ALTER TABLE idx_varchar_big engine=INNODB;
  2. Query OK, 374706 rows affected (10.15 sec)
  3. Records: 374706  Duplicates: 0  WARNINGS: 0
  4.  
  5. mysql> ALTER TABLE idx_varchar_small engine=INNODB;
  6. Query OK, 374706 rows affected (10.56 sec)
  7. Records: 374706  Duplicates: 0  WARNINGS: 0
  8.  
  9. mysql> ALTER TABLE idx_varchar_mixed engine=INNODB;
  10. Query OK, 374706 rows affected (7.27 sec)
  11. Records: 374706  Duplicates: 0  WARNINGS: 0
  12.  
  13. mysql> SHOW table status;
  14. +--------------------------+-----------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------+
  15. | Name                     | Engine    | Version | Row_format | Rows   | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | AUTO_INCREMENT | Create_time         | Update_time         | Check_time          | Collation         | Checksum | Create_options | Comment |
  16. +--------------------------+-----------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------+
  17. | idx_varchar_big          | INNODB    |      10 | Compact    | 375091 |             51 |    19447808 |               0 |     13172736 |   5242880 |           NULL | 2009-08-22 16:43:50 | NULL                | NULL                | latin1_swedish_ci |     NULL |                |         |
  18. | idx_varchar_mixed        | INNODB    |      10 | Compact    | 375257 |             34 |    13123584 |               0 |      6832128 |   4194304 |           NULL | 2009-08-22 16:44:31 | NULL                | NULL                | latin1_swedish_ci |     NULL |                |         |
  19. | idx_varchar_small        | INNODB    |      10 | Compact    | 375257 |             34 |    13123584 |               0 |      6832128 |   4194304 |           NULL | 2009-08-22 16:44:08 | NULL                | NULL                | latin1_swedish_ci |     NULL |                |         |
  20. +--------------------------+-----------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------+
  21. 3 rows in SET (0.01 sec)

Apparently, the same initial conclusion apply to InnoDB (except for the rant on the packed index, which is MyISAM specific). Looking at the file sizes (innodb_file_per_table):

CODE:
  1. [root@speedy ~]# ls -la /var/lib/mysql/test/idx_varchar_{small,big,mixed}.ibd
  2. -rw-rw---- 1 mysql mysql 41943040 Ago 22 16:43 /var/lib/mysql/test/idx_varchar_big.ibd
  3. -rw-rw---- 1 mysql mysql 28311552 Ago 22 16:44 /var/lib/mysql/test/idx_varchar_mixed.ibd
  4. -rw-rw---- 1 mysql mysql 28311552 Ago 22 16:44 /var/lib/mysql/test/idx_varchar_small.ibd

Good to know.

AddThis Social Bookmark Button

The bigger smaller than the smaller one

August 20th, 2009 ntavares Posted in en_US, mysql, performance 4 Comments »

Ouvir com webReader

I was trying to determine if the storage size of a VARCHAR field in MySQL had any fixed influence in the key size. I've created a few tables, but an interesting thing came out, as you will see. Let's create the test tables:

MySQL:
  1. CREATE TABLE idx_varchar_size ( a VARCHAR(5) NOT NULL, b VARCHAR(20) NOT NULL ) ENGINE=MyISAM;
  2.  
  3. INSERT INTO idx_varchar_size('abcef','1234567890123456789012345678901234567890');

I did this a couple of times:

MySQL:
  1. INSERT INTO idx_varchar_size SELECT * FROM idx_varchar_size;

I actually used this table to be the source data to feed into the test tables:

MySQL:
  1. mysql> CREATE TABLE idx_varchar_mixed ( a VARCHAR(20) NOT NULL, key idx_big(a) ) ENGINE=MyISAM;
  2. Query OK, 0 rows affected (0.01 sec)
  3.  
  4. mysql> CREATE TABLE idx_varchar_big ( a VARCHAR(20) NOT NULL, key idx_big(a) ) ENGINE=MyISAM;
  5. Query OK, 0 rows affected (0.00 sec)
  6.  
  7. mysql> CREATE TABLE idx_varchar_small ( a VARCHAR(5) NOT NULL, key idx_small(a) ) ENGINE=MyISAM;
  8. Query OK, 0 rows affected (0.01 sec)
  9.  
  10. mysql> INSERT INTO idx_varchar_small SELECT a FROM idx_varchar_size ;
  11. Query OK, 374706 rows affected (2.04 sec)
  12. Records: 374706  Duplicates: 0  WARNINGS: 0
  13.  
  14. mysql> INSERT INTO idx_varchar_big SELECT b FROM idx_varchar_size ;
  15. Query OK, 374706 rows affected (3.38 sec)
  16. Records: 374706  Duplicates: 0  WARNINGS: 0
  17.  
  18. mysql> INSERT INTO idx_varchar_mixed SELECT a FROM idx_varchar_size ;
  19. Query OK, 374706 rows affected (3.06 sec)
  20. Records: 374706  Duplicates: 0  WARNINGS: 0

So I've created a small dataset, a "big" dataset, and a "big" schema holding a small dataset. Let's see the output of SHOW TABLE STATUS:

MySQL:
  1. mysql> SHOW table status;
  2. +-------------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------+
  3. | Name              | Engine | Version | Row_format | Rows   | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | AUTO_INCREMENT | Create_time         | Update_time         | Check_time          | Collation         | Checksum | Create_options | Comment |
  4. +-------------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------+
  5. | idx_varchar_big   | MyISAM |      10 | Dynamic    | 374706 |             24 |     8992944 | 281474976710655 |       818176 |         0 |           NULL | 2009-08-20 14:33:52 | 2009-08-20 14:34:07 | 2009-08-20 14:34:09 | latin1_swedish_ci |     NULL |                |         |
  6. | idx_varchar_mixed | MyISAM |      10 | Dynamic    | 374706 |             20 |     7494120 | 281474976710655 |       798720 |         0 |           NULL | 2009-08-20 14:32:28 | 2009-08-20 14:34:33 | 2009-08-20 14:34:35 | latin1_swedish_ci |     NULL |                |         |
  7. | idx_varchar_size  | MyISAM |      10 | Dynamic    | 374706 |             32 |    11990592 | 281474976710655 |      5514240 |         0 |           NULL | 2009-08-20 13:02:49 | 2009-08-20 13:06:23 | NULL                | latin1_swedish_ci |     NULL |                |         |
  8. | idx_varchar_small | MyISAM |      10 | Dynamic    | 374706 |             20 |     7494120 | 281474976710655 |      4599808 |         0 |           NULL | 2009-08-20 14:32:40 | 2009-08-20 14:32:53 | 2009-08-20 14:32:54 | latin1_swedish_ci |     NULL |                |         |
  9. +-------------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+-------------------+----------+----------------+---------+
  10. 4 rows in SET (0.00 sec)

Well, this is odd. My small table as Index_length a lot bigger (5 times) than my ''big'' dataset?? Maybe it's a SHOW TABLE STATUS bug, let's see how much space the data files actually use.

CODE:
  1. [root@speedy test]# ls -la /var/lib/mysql/test/idx_varchar_{big,mixed,small}*.MYI
  2. -rw-rw---- 1 mysql mysql  818176 Ago 20 14:34 /var/lib/mysql/test/idx_varchar_big.MYI
  3. -rw-rw---- 1 mysql mysql  798720 Ago 20 14:34 /var/lib/mysql/test/idx_varchar_mixed.MYI
  4. -rw-rw---- 1 mysql mysql 4599808 Ago 20 14:32 /var/lib/mysql/test/idx_varchar_small.MYI

Nope. It's true. After a quick thinking, I reminded that MySQL can pack the keys and now this resembles the benefit of packed indexes. Let's make a simple comparison with an explicited packed key:

MySQL:
  1. mysql> CREATE TABLE idx_varchar_small_packed ( a VARCHAR(5) NOT NULL, key idx_small(a) ) ENGINE=MyISAM PACKED_KEYS=1;
  2. Query OK, 0 rows affected (0.01 sec)
  3.  
  4. mysql> INSERT INTO idx_varchar_small_packed SELECT a FROM idx_varchar_size ;
  5. Query OK, 374706 rows affected (2.04 sec)
  6. Records: 374706  Duplicates: 0  WARNINGS: 0

CODE:
  1. [root@speedy test]# ls -la /var/lib/mysql/test/idx_varchar_small_packed.MYI
  2. -rw-rw---- 1 mysql mysql  798720 Ago 20 18:14 /var/lib/mysql/test/idx_varchar_small_packed.MYI

Indeed, it does - it's the same size as idx_varchar_mixed. But it already seems to answer our initial question: VARCHAR size won't influence the key size unnecessary (compare idx_varchar_mixed with idx_varchar_small_packed).

But now I got curious about the smaller size of the key for the bigger dataset. Is it feasible to assume that the MyISAM storage engine, when PACK_KEYS is not specified, it auto-selects a minimum length for VARCHARs which it thinks it worths packing them? The documentation makes no reference to it:

PACK_KEYS takes effect only with MyISAM tables. Set this option to 1 if you want to have smaller indexes. This usually makes updates slower and reads faster. Setting the option to 0 disables all packing of keys. Setting it to DEFAULT tells the storage engine to pack only long CHAR, VARCHAR, BINARY, or VARBINARY columns.

MySQL:
  1. CREATE TABLE idx_varchar_big2 ( a VARCHAR(20) NOT NULL, key idx_big(a) ) ENGINE=MyISAM PACK_KEYS=1;
  2. Query OK, 0 rows affected (0.01 sec)
  3.  
  4. INSERT INTO idx_varchar_big2 SELECT * FROM idx_varchar_big ;
  5. Query OK, 374706 rows affected, 65535 WARNINGS (2.27 sec)
  6. Records: 374706  Duplicates: 0  WARNINGS: 0

CODE:
  1. [root@speedy test]# ls -la /var/lib/mysql/test/idx_varchar_{big,mixed,small,vbig}*.MYI
  2. -rw-rw---- 1 mysql mysql  818176 Ago 20 18:52 /var/lib/mysql/test/idx_varchar_big2.MYI
  3. -rw-rw---- 1 mysql mysql  818176 Ago 20 14:34 /var/lib/mysql/test/idx_varchar_big.MYI

So they match the very same bytes and now I want to know which 'minimum' is that!. I'll be creating many tables (using a simple mental algorithm to speed up) until the packed index pops up. I'll also use the 'b' field from idx_varchar_size to fill each test table column completely to force the key to be bigger (see what otherwise happened with idx_varchar_mixed!), so ignore the warnings after the INSERT INTO. I eventually came up to the split value:

MySQL:
  1. CREATE TABLE idx_varchar_small7 ( a VARCHAR(7) NOT NULL, key idx_verybig(a) ) ENGINE=MyISAM;
  2. Query OK, 0 rows affected (0.01 sec)
  3.  
  4. CREATE TABLE idx_varchar_small8 ( a VARCHAR(8) NOT NULL, key idx_verybig(a) ) ENGINE=MyISAM;
  5. Query OK, 0 rows affected (0.01 sec)

MySQL:
  1. mysql> INSERT INTO idx_varchar_small7 SELECT b FROM idx_varchar_size;
  2. Query OK, 374706 rows affected, 65535 WARNINGS (2.25 sec)
  3. Records: 374706  Duplicates: 0  WARNINGS: 374706
  4.  
  5. mysql> INSERT INTO idx_varchar_small8 SELECT b FROM idx_varchar_size;
  6. Query OK, 374706 rows affected, 65535 WARNINGS (2.34 sec)
  7. Records: 374706  Duplicates: 0  WARNINGS: 374706

CODE:
  1. [root@speedy test]# ls -la /var/lib/mysql/test/idx_varchar_small?.MYI
  2. -rw-rw---- 1 mysql mysql 5366784 Ago 20 20:07 /var/lib/mysql/test/idx_varchar_small7.MYI
  3. -rw-rw---- 1 mysql mysql  801792 Ago 20 20:08 /var/lib/mysql/test/idx_varchar_small8.MYI

I really suspect this value is some kind of measure of efficiency.

I'll postpone (keep reading) some calculations on this because now just popped up a question about our conclusion to the initial question: Does the size of a VARCHAR field in MySQL have any fixed influence in a NOT packed key size?

To answer that, let's create the 'small' and 'big' tables with explicit PACK_KEYS=0:

MySQL:
  1. mysql> CREATE TABLE idx_varchar_small_nopack ( a VARCHAR(5) NOT NULL, key idx_small(a) ) ENGINE=MyISAM PACK_KEYS=0;
  2. Query OK, 0 rows affected (0.01 sec)
  3.  
  4. mysql> CREATE TABLE idx_varchar_mixed_nopack ( a VARCHAR(20) NOT NULL, key idx_small(a) ) ENGINE=MyISAM PACK_KEYS=0;
  5. Query OK, 0 rows affected (0.02 sec)
  6.  
  7. mysql> INSERT INTO idx_varchar_small_nopack SELECT a FROM idx_varchar_size;       
  8. Query OK, 374706 rows affected (2.47 sec)
  9. Records: 374706  Duplicates: 0  WARNINGS: 0
  10.  
  11. mysql> INSERT INTO idx_varchar_mixed_nopack SELECT a FROM idx_varchar_size;   
  12. Query OK, 374706 rows affected (3.20 sec)
  13. Records: 374706  Duplicates: 0  WARNINGS: 0

CODE:
  1. [root@speedy ~]# ls -la /var/lib/mysql/test/idx_varchar*{nopack,small,mixed}.MYI
  2. -rw-rw---- 1 mysql mysql  798720 Ago 20 14:34 /var/lib/mysql/test/idx_varchar_mixed.MYI
  3. -rw-rw---- 1 mysql mysql 4599808 Ago 21 00:23 /var/lib/mysql/test/idx_varchar_mixed_nopack.MYI
  4. -rw-rw---- 1 mysql mysql 4599808 Ago 20 14:32 /var/lib/mysql/test/idx_varchar_small.MYI
  5. -rw-rw---- 1 mysql mysql 4599808 Ago 21 00:22 /var/lib/mysql/test/idx_varchar_small_nopack.MYI

These new 'nopack' tables are indeed of the same size, so it's safe to say:

VARCHAR size won't influence the key size unnecessary

The efficiency of a packed key entry

The VARCHAR index entry is tipically like this:

1 (number of bytes of same prefix) + N (bytes of different suffix) 4 (record pointer size)

I'll remeber that my datasets were Latin1, so each character matches to a single byte. Now 8-5=3. If we had packed keys for a VARCHAR(6) and the data was kind of sequential for each record (like rows being generated by nested loops, such as "aaa", "aab", "aac", etc), thus unique but highly ''packable'', the sum of bytes would be something like this:

1 (number of bytes of same prefix) + 1 (bytes of different suffix) 4 (record pointer size) = 6

This packed index entry size matches the length of VARCHAR; everything below 6 would waste overhead with the first byte for the prefix for no gain at all, right? Which means that to be worthy, PACKED_KEYS should be applied to VARCHARs bigger than 6. Assuming that there is a bytecode separator (High Performance MySQL uses an analogy with a colon, like in "5,a" or "5,b"), we can shift that rule to:

To be worthy, PACKED_KEYS should be applied to VARCHARs bigger than 7!

Now we should confirm there is indeed a separator. I thought of using an hex viewer to see if I could come with a pattern. The best would be to look at MYI specification (either in MySQL source code or MySQL Documentation/Wiki):

HTML:
  1. 00000000  fe fe 07 01 00 03 01 4d  00 b0 00 64 00 c4 00 01  |.......M...d....|
  2. 00000010  00 00 01 00 08 01 00 00  00 00 30 ff 00 00 00 00  |..........0.....|
  3. [... typical file format heading, some 00 and ff ...]
  4. 00000400  03 fd 00 08 31 32 33 34  35 36 37 38 00 00 00 00  |....12345678....|
  5. 00000410  00 00 0e 14 0e 28 0e 3c  0e 50 0e 64 0e 78 0e 8c  |.....(.<.P.d.x..|
  6. 00000420  0e a0 0e b4 0e c8 0e dc  0e f0 0d 01 04 0e 18 0e  |................|
  7. 00000430  2c 0e 40 0e 54 0e 68 0e  7c 0e 90 0e a4 0e b8 0e  |,.@.T.h.|.......|
  8. 00000440  cc 0e e0 0e f4 0d 02 08  0e 1c 0e 30 0e 44 0e 58  |...........0.D.X|
  9. 00000450  0e 6c 0e 80 0e 94 0e a8  0e bc 0e d0 0e e4 0e f8  |.l..............|
  10. [...]
  11. 000c3300  0e 80 0e 94 0e a8 0e bc  0e d0 0e e4 0e f8 0d 59  |...............Y|
  12. 000c3310  0c 0e 20 0e 34 0e 48 0e  5c 0e 70 0e 84 0e 98 0e  |.. .4.H.\.p.....|
  13. 000c3320  ac 0e c0 0e d4 00 00 00  00 00 00 00 00 00 00 00  |................|
  14. 000c3330  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
  15. *
  16. 000c3400  80 67 00 00 00 00 03 02  00 08 31 32 33 34 35 36  |.g........123456|
  17. 000c3410  37 38 00 00 00 71 0d 18  00 00 00 00 03 04 0d 32  |78...q.........2|
  18. [... nonsense data (to me) that could be a file format footer/terminator ...]
  19. 000c3860  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
  20. *
  21. 000c3c00

Notice the data is shown in plain text('12345678'). The [...] indicates content that shows the pattern (sorry can't highlight it) -- actually, it seems to be repeated in intervals of 0x400 (this value definitely has a name and a resason). Now:

  • The "number of bytes of same prefix" should be a constant, and that's probably the value that causes the pattern. I can't explain why it's 0x0e and not 0x08, but that's definitely overhead for some reason.
  • My different suffix is "", since the data is repeated, which is 0 bytes (non-existent) in "bytes of different suffix" above.
  • More, our data is stored adjacentely (the order of "referred rows" in the index is the same as in the data file), and we know that PACKED_KEYS can also pack "record pointer size" above. The offset for the next record (in the data file) is almost certainly smaller than 256 - even with an eventual MYD format overhead, the only field is 8 chars/bytes long -, so it can easily fit on a single byte, which is the way an intelligent packing of the pointer size the should be done.

Therefore, I suspect that a packed key row would take 1+1=2 bytes, forming a kind of pattern composed by the first byte a constant value, and the second an evenly increasing value - although there isn't an ''index entry separator''. And that's just what we're looking at the above hexdump: the second one is increasing 0x14 (20d) sequentially, and that also suggests each record (of 8 bytes) in the data file is actually stored in 20 bytes blocks.

Of course I should fundament some assumptions by looking at the specs, but it seems pretty obvious: and I just wanted to test what I completed minutes ago. Testes were done with MySQL 5.1.37 on an x86. :-)

AddThis Social Bookmark Button

Benchmarking de parâmetros InnoDB

July 15th, 2009 ntavares Posted in mysql, performance, pt_PT No Comments »

Ouvir com webReader

Aqui vão uns benchmarks que fiz uma vez com InnoDB para uma tabela quase exclusivamente de escrita. Com quase exclusivamente quero dizer que 99.99% das operações eram INSERTs. O filesystem, desta vez, era ZFS, e era o mesmo para os logs e datafiles.

Vamos aos testes. Especificações de hardware e SO:

  • MySQL: 5.1.25-rc-log conforme consta no CoolStack
  • Intel Quad core x 2800 MHz
  • 16 GB RAM
  • Solaris 10

As configurações de base eram estas:

CODE:
  1. transaction-isolation = READ-COMMITTED
  2. innodb_file_per_table
  3. innodb_buffer_pool_size = 3000M
  4. innodb_additional_mem_pool_size = 20M
  5. innodb_log_file_size = 256M
  6. innodb_autoinc_lock_mode = 2
  7. innodb_flush_log_at_trx_commit = 2
  8. innodb_file_per_table
  9. log-slow-queries=mysql-query-slow.log
  10. slow_query_log = 1
  11. long_query_time = 1
  12. innodb_doublewrite = 0

E todos os testes foram intercalados de DROP TABLE, CREATE TABLE e uma única alteração à configuração de base. Os INSERT's foram feitos com INSERT .... ON DUPLICATE KEY. O schema anda algures perdido aqui nos meus apontamentos (vou tentar encontrá-lo entranto) mas é de notar que havia um único índice, que era um UNIQUE KEY, e que foi transformado inicialmente para PK e deixado em alguns dos testes seguintes.

A tabela seguinte diz respeito ao único parâmetro que foi alterado em relação à base:

CODE:
  1. Serie A default
  2. Serie B UNIQUE -> PRIMARY KEY
  3. Serie C PK, Autocommit = 0 (1 comm/seg)
  4. Serie D PK, innodb_flush_method = O_DIRECT
  5. Serie F PK, innodb_flush_method = O_DSYNC
  6. Serie G innodb_flush_log_at_trx_commit = 0
  7. Serie H innodb_doublewrite = 0
  8. Serie I innodb_log_file_size = 128M
  9. Serie J zfs set recordsize=16k data/mysql

innodb-parameters-1

Na Série B a única alteração foi a conversão da UNIQUE KEY para uma PRIMARY KEY, nada a assinalar. Na Série C, a alteração consistia em suster os COMMIT's durante aprox. 1 segundo, fazendo COMMIT a cada segundo. Mal seria se não tivéssemos um ganho, por mínimo que fosse, mas é preciso ter em conta que, apesar do ganho ser de 20%, são ~7600 potenciais candidatos a rollback caso alguma coisa corra mal nesse segundo..! Posso dizer que o rollback de aprox. metade demora bastante (para o que é um arranque normal do MySQL).

Queria também experimentar Direct I/O com InnoDB na Série D mesmo sabendo que estava em ZFS, e que a coisa não deveria ser tão fácil. O erro foi:

CODE:
  1. 090721 18:18:10  InnoDB: Failed to set DIRECTIO_ON on file /opt/coolstack/mysql/data/ibdata1: OPEN: Inappropriate ioctl for device, continuing anyway

Na série F, foi a vez de experimentar O_DSYNC, e nem acabei de terminar o teste, pois iria demorar demasiado tempo. Escusado será dizer que o I/O foi altíssimo durante esse momento. Comecei entretanto a procurar maneiras de afinar o ZFS, mas convenhamos que ao fim de uns minutos a ler, já estava a enveredar por um caminho muito muito distante.. :-) É incrível a quantidade de coisas que dá para fazer com ZFS e só por si vai merecer uma categoria própria, um dia...

De volta ao MySQL, a Série G inflinge um risco conhecido, desleixando o flush dos logs, por isso não é de estranhar o aumento de performance - esta opção deve ser analisada com cuidado, pois tem contrapartidas. A Série H desactivou o doublewrite do motor, resultando num aumento de performance de 3,5% (relativamente à Série A) conforme previsto pelo Peter Zaitsev. Como estamos perante ZFS, não vejo motivo nenhum para não desactivá-lo.

Reduzindo o tamanho dos logs na Série I resultou num aumento de performance de 2,8%. Isto é interessante, e é a prova viva de que, se por um lado precisamos deles grandes, por outro, a sua gestão pode infligir mais carga ou deteriorar a performance por serem grandes demais, isto para não falar na forma como afectam o recovery. Não cheguei a testar, mas com um tamanho ainda mais pequeno, o aumento podia ser maior...

A Série J foi dedicada ao ZFS, com a recomendação típica para filesystems dedicados a DBs, que é o alinhamento do tamanho dos blocos com o tamanho das páginas de InnoDB (16KB), e o ZFS, como [quase?] todos os sistemas de ficheiros, permite ajustar esse parâmetro. Traduzindo, a cada leitura do disco, o SO pede ao disco record size (omissão: 128KB) bytes de cada vez; o InnoDB, que tenta gerir os acessos I/O de forma inteligente minimizando-os ao máximo, faz pedidos de page size bytes de cada vez. Se o SO não estiver alinhado, e como os blocos pedidos pelo InnoDB são na maioria aleatórios, cada bloco de 16K solicitado pelo InnoDB traduzir-se-á em leituras de 128KB. Com um pedido de 10 páginas aleatórias (160KB), o SO terá de ler 1280KM, ou seja, quase 10x mais! Mas como eu estava à espera, o resultado não foi significativo (1%) já que este cenário era exclusivamente de INSERTs.

Em termos de sistemas de ficheiros, sejam ZFS ou outro qualquer, há ainda características determinantes a considerar, que são o efeito de prefetching e read-ahead.... mas lá está, já me estava a desviar demasiado do MySQL :P Aqui ficam algumas considerações interessantes sobre o uso de MySQL em ZFS.

Ficaram a faltar muitas opções por falta de tempo, mas aqui ficam sugestões para a quem quiser experimentar:

AddThis Social Bookmark Button

Este fim de semana…

July 13th, 2009 ntavares Posted in lazer, mysql, programming, pt_PT, scaling 1 Comment »

Ouvir com webReader

Desta vez, num capítulo mais pessoal, os planos do fim-de-semana saíram furados. A minha opinião, que nem sequer me dei ao trabalho de me informar muito, é que espero bem que a SBSR e Música no Coração tenham sido indemnizados por isto... já vamos às críticas como as que constam no artigo do Público, mas não deve ser fácil conseguir trazer bandas deste nível, e a responsabilidade é tão grande que posso deduzir o esforço da organização em que tudo corresse bem... da nossa parte! Claro que ninguém ia imaginar que a florzinha não ia conseguir cantar [com as cordas vocais, sabem como é?] porque tinha uma lesão na perna... gosto muito de Depeche Mode, não conheço a gravidade da situação, mas não achei nada bem este cancelamento na véspera [estou a assumir que a SBSR no-la comunicou assim que tomaram conhecimento]. E, ao que parece, já o ano passado tinham cancelado o concerto.. Devem haver seguros para estas situações, e a falta de público no Porto deve ser coberta/indemnizada! Claro que não fui: eu não ia pelo festival, mas exclusivamente para ver Depeche Mode - a coincidência com Novelle Vague era excelente, porque já conheço os concertos deles e são sempre muito bons. Mas cada macaco no seu galho, esta era a noite de Depeche, e a responsabilidade pelo fracasso é deles.

Quanto aos comentários no artigo do Público, e à sensação de desilusão do público... mais uma vez estou do lado do SBSR, que ainda assim conseguiu oferecer alternativa, mesmo com o prejuízo que já se adivinhava... Xutos & Pontapés são excepcionais, The Gift também, mas quem é que podiam ter convidado na véspera do acontecimento?? Mas cada macaco no seu galho... e estes já os vi... algumas vezes :-)

Se bem que vão sempre acontecendo surpresas: lembro-me de ir ao Sudoeste de propósito para ver os The Cure e de terminar o espectáculo desiludido com eles. Porém, assisti a um espectacular concerto de Peter Murphy - que nem conhecia, vejam lá.. -, dos infalíveis Blasted Mechanism, e Chemical Brothers

Mas bem, o fim-de-semana foi salvo [com grande categoria] com uma viagem ao belíssimo Convento de Cristo [é uma vergonha - minha - que o artigo na Wikipédia esteja tão pequeno], que já de si nos deixa emocionados, sensação essa que foi agravada pela música de Nightwish - Ghost Love Score (clip), no regresso. Já os deixei passar em 2008, mas não penso que vá voltar a acontecer..

Andei a alternar o tema deste blog. Ainda não encontrei nenhum que me satisfizesse, então voltei à base, já que o último (POP Blue?!) além de ter um estilo duvidoso, nem sequer mostrava os posts completos, mas apenas uma versão condensada.. entretanto activei o reCaptcha e a malta fora da DRI já pode comentar... sejam brandos!

Ao cruzar-me com o blog do Marcos, fui parar a uma rant sobre concorrência que, por sua vez, me levou a ver este vídeo disparatado sobre o paralelismo de Erlang :-) Ele há cada um... mas interessante foi revisitar os conceitos do MapReduce e do Hadoop. Estes conceitos vão-se tornando cada vez mais pertinentes numa altura em que o volume de dados a processar se torna inimaginável - e a Google sabe-o melhor que ninguém. Do tal rant fui parar a uma exposição sobre a arquitectura do LinkedIn e, tristeza das tristezas, acabei por perceber que perdi uma belíssima oportunidade de obter os produtos da Atlassian por $5. O engraçado é que agora o link para o pedido é feito na forma vote para trazer a promoção de volta! Se é para captura de Leads, achei engraçado! Toca a votar!!

E, nesta linha de orientação, aproveito para deixar uma demonstração do KickFire (a appliance dedicada a correr MySQL), que curiosamente tinha sido apresentado na edição do ano passado. O título diz tudo: Do You Believe in Magic?. Para quem não sabe, a abordagem da KickFire é uma mistura de um CPU dedicado, um storage engine baseado em colunas, e o que eles chamam de stream processing, que é mais ou menos aquilo que nós vamos fazendo inconscientemente.

Perdi [hrm,hrm, investi] finalmente um bom tempo a ver as apresentações da MySQL Conference & Expo 2009 [slides e vídeo] (e alguns de 2008), de onde destaco: mysqlnd: How the PHP/MySQL Stack Got Better, que será uma nova abordagem à ligação do PHP com MySQL, Understanding and Control of MySQL Query Optimizer: Traditional and Novel Tools and Techniques, com aspectos muito interessantes sobre o percurso do Query Optimizer na elaboração do plano.

Estas conferências têm sido arrebatadoras. Para o ano vou [ou talvez não... :(]!

AddThis Social Bookmark Button

MySQL DATETIME vs TIMESTAMP vs INT performance and benchmarking with InnoDB

July 6th, 2009 ntavares Posted in en_US, linux driver, mysql, performance, sugarcrm No Comments »

Ouvir com webReader

Following my tests with DATETIME vs vs TIMESTAMP vs INT performance and benchmarking with MyISAM storage engine, I've wondered about the performance impact using InnoDB, which is usually more peaky with I/O. Read the rest of this entry »

AddThis Social Bookmark Button

MySQL DATETIME vs TIMESTAMP vs INT performance and benchmarking with MyISAM

July 6th, 2009 ntavares Posted in en_US, mysql, performance 9 Comments »

Ouvir com webReader

Recently there was a discussion about DATETIME vs TIMESTAMP vs INT performance and storage requirements. If one setup was bound to disk usage, one would expect INT to perform better, since storage requirements are smaller. However, the amount of calculations to use it as a timestamp can be overwhelming as well. Read the rest of this entry »

AddThis Social Bookmark Button

Analysing MySQL slow queries

July 4th, 2009 ntavares Posted in en_US, mysql, performance No Comments »

Ouvir com webReader

While I'm engaged in my MySQL DBA mode, I usually come across the hard task of surfing around the slow query log.

Everybody trying to trace MySQL performance problems will hit the slow query log to keep track of those unperformant queries that seem to be hogging the system. The traditional tool to analyze the log is mysqldumpslow (provided with standard MySQL distribution) which aggregates the queries by pattern (fingerprint) and calculates some aggregated statistics. I personally find the tool very limited, and I don't seem to be the one (see below). Read the rest of this entry »

AddThis Social Bookmark Button