Kamelot Blog

Aller au contenu | Aller au menu | Aller à la recherche

mercredi 1 septembre 2010

mer
01
sep '10

Nombre de résultats d'une recherche SphinxSe

Logo Sphinx Sphinx est un moteur de recherche full-text

On peut l'interroger au travers de son api, SphinxQL, en ligne de commande ou avec l'engine MySql SphinxSE

J'utilise MySqlSE;

SELECT * from INFORMATION_SCHEMA.ENGINES;

ENGINE SUPPORT COMMENT                     TRANSACTIONS  XA      SAVEPOINTS
------ ------- --------------------------- ------------  ------  ----------
...
SPHINX YES     Sphinx storage engine 0.9.9 NO            NO      NO        

La question du jour était : Y a-t-il un moyen de connaître le nombre total de résultats quand on utilise un limit ?

En Mysql simple, il y a SQL_CALC_FOUND_ROWS

mysql> SELECT SQL_CALC_FOUND_ROWS * 
    -> FROM tbl_name
    -> WHERE id > 100 LIMIT 10;
mysql> SELECT FOUND_ROWS();

Le second SELECT retourne un nombre indiquant combien de lignes le premier SELECT aurait retourné s'il n'avait pas été écrit avec une clause LIMIT.

Mais avec avec SPHINX

En testant sur ma table tbl_name_sphinx qui contient 2410 rows.

select SQL_CALC_FOUND_ROWS * from tbl_name_sphinx WHERE query='' LIMIT 10;
SELECT FOUND_ROWS();
Résultat
FOUND_ROWS()
------------
          20

20 parce que c'est la valeur par défaut du limit de sphinx

# limit - amount of matches to retrieve from result set, default is 20; (ref)

En effet si je force ce limit à 10

select SQL_CALC_FOUND_ROWS *
 from tbl_name_sphinx 
 WHERE query=';limit=10';

SELECT FOUND_ROWS();
Résultat

FOUND_ROWS() -> 10

Pourquoi ? parce que c'est sphinx qui fait la vraie recherche et remonte son résultat à MySql

Donc quand je fait

select SQL_CALC_FOUND_ROWS * 
 from tbl_name_sphinx 
 WHERE query=';limit=1000' 
 LIMIT 10;

Je reçois 10 résultats sur 2410 réels et sur les 1000 que sphinx a remonté

Donc

SELECT FOUND_ROWS(); -> affiche 1000 et pas 2410.

Donc je vais monter mon limit à 1000000.

Gloups, je viens de demander à Sphinx de me préparer en résultat de 100K rows, tout renvoyer à mysql dans une table temporaire qui me retournera uniquement les 10 premiers.... Fameux gaspillage

Quand on sait que sphinx ne me retourne que les id et que donc il faut faire un join avec la table de données, ca fait mal.

la solution

SHOW ENGINE SPHINX STATUS;

On oublie le 'SQL_CALC_FOUND_ROWS'

Et on remplace FOUND_ROWS() par SHOW ENGINE SPHINX STATUS;

On remet le limit 10 au niveau de sphinx;

select  * 
 from tbl_name_sphinx 
 WHERE query=';limit=10';

SHOW ENGINE SPHINX STATUS;

Type    Name    Status                                           
------  ------  -------------------------------------------------
SPHINX  stats   total: 1000, total found: 2410, time: 0, words: 0

Bingo j'ai mon info, planquée dans une "chaine" mais je l'ai.

re bingo

 SHOW STATUS LIKE 'sphinx_%';

Mais je préfère INFORMATION_SCHEMA.

SELECT *
 from information_schema.GLOBAL_STATUS
 WHERE VARIABLE_NAME like 'SPHINX%';
VARIABLE_NAME       VARIABLE_VALUE
------------------  --------------
SPHINX_ERROR        208409        
SPHINX_TIME         0             
SPHINX_TOTAL        1000          
SPHINX_TOTAL_FOUND  2410          
SPHINX_WORD_COUNT   0             
SPHINX_WORDS                      

Et je suis un heureux.

Si vous êtes intéressés par Sphinx, voici un bon article pour l'installer.

Si vous vous êtes déjà intéressé à Zend_Search_Lucene, (bien décrit ici) il me semble avoir que celui-ci peut utiliser Sphinx comme backend. je corrigerai si je retrouve la source.

samedi 14 août 2010

sam
14
aoû '10

Recherche sur un champs indexé, qui n'utilise pas l'index

Petite anti-astuce :Utiliser des fonctions dans les conditions sur un champs indexé, annule l'utilisation de l'index

mysql> EXPLAIN SELECT * FROM film WHERE title LIKE 'Tr%'\\G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: film 
type: range 
possible_keys: idx_title 
key: idx_title 
key_len: 767 
ref: NULL 
rows: 15 
Extra: Using where

Une stratégie d'accès rapide à un range est choisi par l'optimiseur, et l'index sur le titre est utilisé pour réduire la quantité d'enregistrements à examiner (ici 15)

mysql> EXPLAIN SELECT * FROM film WHERE LEFT(title,2) = 'Tr' \\G 
*************************** 1. row *************************** 
id: 1 
select_type: SIMPLE 
table: film type: ALL 
possible_keys: NULL 
key: NULL 
key_len: NULL 
ref: NULL 
rows: 951 
Extra: Using where

Une analyse complète des enregistrement, donc lente, (c'est la stratégie d'accès ALL) est utilisée car une fonction (LEFT) opére sur la colonne de titre dans la condition.


source Join-fu: The Art of SQL Tuning for MySQL

dimanche 18 avril 2010

dim
18
avr '10

Timestamp VS Datetime

Jeudi en buvant un verre avec un ami, il me demande la différence entre un Datetime et un Timestamp.

J'ai répondu mais je me doutais que je n'étais pas exhaustif.

J'ai donc un peu relu. Ca fait toujours du bien.

1° la taille

  • Datetime -> 8 bytes
  • alors que timestamp c'est 4 bytes

Si on a pas besoin de stocker une date et une heure, on a d'autres possibilités.

  • Date ou Time c'est 3 Bytes
  • Year C'est 1 byte

2° Les dates représentables

  • Date et DateTime -> Année 1000 à 9999
  • Year -> Année 1901 à 2155
  • Timestamp -> Année 1970 à 2036[1]

3° "default" magiques

Timestamp peut être mis à jour automatiquement à la date serveur lors d'une création ou mise à jour de l'enregistrement.

4° Particularité supplémentaire pour mysql

Les valeurs ``zéro``

  • DATETIME '0000-00-00 00:00:00'
  • DATE '0000-00-00'
  • TIMESTAMP 00000000000000 (la longueur dépend de la taille de l'affichage)
  • TIME '00:00:00'
  • YEAR 0000

Cela peut-être désactivé avec certains modes.


Affichage et format

Bien que les valeurs TIMESTAMP soient stockées avec une précision d'une seconde, la seule fonction qui travaille directement avec ces valeurs est la fonction UNIX_TIMESTAMP(). Les autres fonctions opèrent sur des valeurs lues et formatées.

Notes

[1] En fait c'est 2037 mais pas jusqu'au 31 décembre.

vendredi 18 décembre 2009

ven
18
dec '09

MySQL :: Le type ENUM liste les valeurs qu'on accepte ? -> faux !!

J'ai eu la maladresse de croire que Le type ENUM de Mysql pouvait servir de garde fou.

En effet

[SQL]
create table `test_enum`( 
`id` int UNSIGNED NOT NULL AUTO_INCREMENT , 
`monEnum` enum('a','brol','7') , 
`monAutreEnum` enum('a','brol','7') NOT NULL , 
PRIMARY KEY (`id`)
)  ;

Je ne veux que 'a','brol' ou '7', éventuellement NULL dans monEnum

insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'a','a');
# (1 row(s) affected)


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,NULL,'a');
# (1 row(s) affected)


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'a',NULL);
# Error Code : 1048
# Column 'monAutreEnum' cannot be null


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,NULL,NULL);
# Error Code : 1048
# Column 'monAutreEnum' cannot be null
    id  monEnum  monAutreEnum
------  -------  ------------
     1  a        a           
     2  a        a           
     3  (NULL)   a

Là ca allait.

Mais maintenant

[SQL]
insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'','a');
# (1 row(s) affected, 1 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'a','');
# (1 row(s) affected, 1 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'','');
# (1 row(s) affected, 2 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'Z','a');
# (1 row(s) affected, 1 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'a','Z');
# (1 row(s) affected, 1 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'Z','Z');
# (1 row(s) affected, 2 warning(s))

On voit que les "vides" et les "Z" ne bloquent pas l'ajout de l'enregistrement. et sont remplacés par "vide" alors que ce n'est pas une valeur acceptée.

En effet le manuel dit ceci.

Si vous insérez une valeur illégale dans une énumération ENUM (c'est à dire, une chaîne qui n'est pas dans la liste de valeurs autorisées), la chaîne vide est insérée pour représenter une erreur. Cette chaîne peut être distinguée d'une chaîne vide 'normale' par le fait que cette chaîne à la valeur numérique 0. Nous reviendrons sur ce point plus tard.

Par exemple, une colonne créée comme ENUM("un", "deux", "trois") peut prendre n'importe quelle valeur ci-dessous. L'index de chaque valeur est aussi présenté :

 Valeur     Index
--------- --------
 NULL 	   NULL
 ""         0
 "un"       1
 "deux"     2
 "trois"    3
[Resultat]
    id  monEnum  monAutreEnum
------  -------  ------------
     1  a        a           
     2  a        a           
     3  (NULL)   a           
     4           a           
     5  a                    
     6                       
     7           a           
     8  a                    
     9                       

Notez que si j'ajoute une valeur par défaut, ca n'y change rien

[SQL]
alter table `test_enum` 
change `monEnum` `monEnum` enum('a','brol','7') character set latin1 collate latin1_swedish_ci default 'a' NULL , 
change `monAutreEnum` `monAutreEnum` enum('a','brol','7') character set latin1 collate latin1_swedish_ci default 'a' NOT NULL;


///[SQL]
insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'','a');
# (1 row(s) affected, 1 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'a','');
# (1 row(s) affected, 1 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'','');
# (1 row(s) affected, 2 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'Z','a');
# (1 row(s) affected, 1 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'a','Z');
# (1 row(s) affected, 1 warning(s))


insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'Z','Z');
# (1 row(s) affected, 2 warning(s))
    id  monEnum  monAutreEnum
------  -------  ------------
     1  a        a           
     2  a        a           
     3  (NULL)   a           
     4           a           
     5  a                    
     6                       
     7           a           
     8  a                    
     9                       
    10           a           
    11  a                    
    12                       
    13           a           
    14  a                    
    15                       

En fait tout dépends du mode sql en cours

[SQL]
select @@SQL_MODE;
SET SESSION sql_mode='STRICT_ALL_TABLES';
select @@SQL_MODE;
#@@SQL_MODE       
#-----------------
#STRICT_ALL_TABLES

insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'brol','z');
#Error Code : 1265
#Data truncated for column 'monAutreEnum' at row 1
insert into `test_enum`(`id`,`monEnum`,`monAutreEnum`) values ( NULL,'z','brol');
#Error Code : 1265
#Data truncated for column 'monEnum' at row 1

SET SESSION sql_mode='';
select @@SQL_MODE;

Là mes 2 inserts sont bloqués avec le message

		

lundi 12 octobre 2009

lun
12
oct '09

Les mots réservés de mysql

ADD ALL ALTER
ANALYZE AND AS
ASC ASENSITIVE BEFORE
BETWEEN BIGINT BINARY
BLOB BOTH BY
CALL CASCADE CASE
CHANGE CHAR CHARACTER
CHECK COLLATE COLUMN
CONDITION CONNECTION CONSTRAINT
CONTINUE CONVERT CREATE
CROSS CURRENT_DATE CURRENT_TIME
CURRENT_TIMESTAMP CURRENT_USER CURSOR
DATABASE DATABASES DAY_HOUR
DAY_MICROSECOND DAY_MINUTE DAY_SECOND
DEC DECIMAL DECLARE
DEFAULT DELAYED DELETE
DESC DESCRIBE DETERMINISTIC
DISTINCT DISTINCTROW DIV
DOUBLE DROP DUAL
EACH ELSE ELSEIF
ENCLOSED ESCAPED EXISTS
EXIT EXPLAIN FALSE
FETCH FLOAT FLOAT4
FLOAT8 FOR FORCE
FOREIGN FROM FULLTEXT
GOTO GRANT GROUP
HAVING HIGH_PRIORITY HOUR_MICROSECOND
HOUR_MINUTE HOUR_SECOND IF
IGNORE IN INDEX
INFILE INNER INOUT
INSENSITIVE INSERT INT
INT1 INT2 INT3
INT4 INT8 INTEGER
INTERVAL INTO IS
ITERATE JOIN KEY
KEYS KILL LABEL
LEADING LEAVE LEFT
LIKE LIMIT LINES
LOAD LOCALTIME LOCALTIMESTAMP
LOCK LONG LONGBLOB
LONGTEXT LOOP LOW_PRIORITY
MATCH MEDIUMBLOB MEDIUMINT
MEDIUMTEXT MIDDLEINT MINUTE_MICROSECOND
MINUTE_SECOND MOD MODIFIES
NATURAL NOT NO_WRITE_TO_BINLOG
NULL NUMERIC ON
OPTIMIZE OPTION OPTIONALLY
OR ORDER OUT
OUTER OUTFILE PRECISION
PRIMARY PROCEDURE PURGE
RAID0 READ READS
REAL REFERENCES REGEXP
RELEASE RENAME REPEAT
REPLACE REQUIRE RESTRICT
RETURN REVOKE RIGHT
RLIKE SCHEMA SCHEMAS
SECOND_MICROSECOND SELECT SENSITIVE
SEPARATOR SET SHOW
SMALLINT SONAME SPATIAL
SPECIFIC SQL SQLEXCEPTION
SQLSTATE SQLWARNING SQL_BIG_RESULT
SQL_CALC_FOUND_ROWS SQL_SMALL_RESULT SSL
STARTING STRAIGHT_JOIN TABLE
TERMINATED THEN TINYBLOB
TINYINT TINYTEXT TO
TRAILING TRIGGER TRUE
UNDO UNION UNIQUE
UNLOCK UNSIGNED UPDATE
UPGRADE USAGE USE
USING UTC_DATE UTC_TIME
UTC_TIMESTAMP VALUES VARBINARY
VARCHAR VARCHARACTER VARYING
WHEN WHERE WHILE
WITH WRITE X509
XOR YEAR_MONTH ZEROFILL
Je l'avais déjà posté mais j'en ai eu besoin aujourd'hui. Donc comme c'est toujours utile, je le rappelle.

lundi 31 août 2009

lun
31
aoû '09

Comment démarrer un Cluster Mysql 7.0 avec 2 noeuds ?

Une explication pour démarrer à partir de zéro un cluster MySQL 7.0.7 (ou supérieur) avec 2 noeuds de gestion. C'est dans l'article How to start MySQL Cluster 7.0 with 2 magement nodes? de notre compatriote Geert.

samedi 13 juin 2009

sam
13
juin '09

Trouver les Fulltext pour les mises à jour mysql 5.1.

Comme expliqué ici.

si vous passez à 5.1.12

Incompatible change: For utf8 columns, the full-text parser incorrectly considered several nonword 
punctuation and whitespace characters as word characters, causing some searches to return 
incorrect results. The fix involves a change to the full-text parser in MySQL 5.1.12, 
so as of 5.1.12, any tables that have FULLTEXT indexes on utf8 columns must be repaired with REPAIR TABLE:

REPAIR TABLE tbl_name QUICK;

Si vous passez à 5.1.16

Il sera nécéssaire de réindexer les fulltext

Incompatible change: The structure of FULLTEXT indexes 
has been changed in MySQL 5.1.6. After upgrading to 
MySQL 5.1.6 or greater, any tables that have FULLTEXT 
indexes must be repaired with REPAIR TABLE:

REPAIR TABLE tbl_name QUICK;

Comment trouver facilement ces tables ?

 SELECT `TABLE_NAME` FROM `STATISTICS` WHERE `INDEX_TYPE`= 'FULLTEXT' ;

lundi 27 avril 2009

lun
27
avr '09

Timestamp

Pour faire le point sur Timestamp : un bel article sur le sujet.

timestamp, ou Unix Timestamp, correspond au nombre de secondes écoulées depuis le 1er Janvier 1970.

A lire aussi : La page dans le manuel

mardi 24 février 2009

mar
24
fév '09

mysql archive et partition

Petite expérience sur mysql 5.1

4 tables de même structure mais 4 stockages différents : MyISAM et archive, avec et sans partition. un peu plus de 500 enregistrements ....

sans partition

MyISAM 52Ko ->Archive : 20 Ko

avec partition

Myisam 63 Ko -> Archive 12.1Ko

Là je suis étonné, l'archive avec partition est 40% plus petite.

J'ai ajouté 31000 enregistrements

sans partition

  • MyISAM : 52Ko -> 1.59Mo
  • Archive : 20 Ko -> 90Ko !!!!

avec partition

  • MyISAM : 63 Ko -> 1.6Mo
  • Archive : 12.1Ko -> 82Ko

2 grande conclusions

  • L'archive ça vaut vraiment le coup quand on peut supporter ses limites
  • Le partitionnement ne change rien (il y a des différences négligeables)

Je reste quand même étonné que le partitionnement d'une table archive réduit le stockage.

Voir la suite pour plus de détails

Lire la suite...

vendredi 20 février 2009

ven
20
fév '09

Créer une file de traîtement avec Innodb

Ce post est à moitié une manière de faire , et une autre une façon de mieux le faire?

Donc, vous voulez construire un système qui effectue des tâches. Vous voulez que le travail puisse être organisé en parallèle pour la vitesse, mais aussi pour la redondance. Ce système doit être coordonnée de façon, par exemple, les mêmes tâches ne sont pas faites deux fois, le statut de chaque tâche est facile à voir, et de multiples serveurs peuvent effectuer les tâches simplement en interrogeant la source centrale.

Voici la traduction de Creating a Job queue in Innodb Comment peut-on construire cela avec innodb pour avoir MySQL comme système central de notre système?

[MYSQL]
CREATE TABLE IF NOT EXISTS job_queue(
   id int(10) not null auto_increment,

   updated timestamp not null,
   started timestamp not null,

   state ENUM('NEW', 'WORKING', 'DONE', 'ERROR' ) default 'NEW',

   PRIMARY KEY ( id ),
   KEY( STATE )
) ENGINE=Innodb;

Dans ce schéma, notre table de tâches a un identifiant unique, une heure de démarrage et de mise à jour et un statut.

Dans un système réel, il y aura sans doute un peu plus de méta-données ici sur la nature des tâches.

Lorsque de nouvelles tâches doivent être programmées, les lignes sont insérées dans la table (avec started à NOW (), et updated est mis à jour automatiquement)

[MYSQL]
insert into job_queue set started=NOW();

Maintenant, en perl, nous écrivons un contrôleur de tâches. Ce programme peut être exécuté sur chaque serveur qui pourrait accomplir les tâches.

[perl]
#!/usr/bin/perl

# Don't leave zombies around, shamelessly stolen from 'man perlipc'
use POSIX ":sys_wait_h";
sub REAPER {
    my $child;
    while (($child = waitpid(-1,WNOHANG)) > 0) {
        $Kid_Status{$child} = $?;
    }
    $SIG{CHLD} = \&REAPER;
}
$SIG{CHLD} = \&REAPER;

use DBD::mysql;

my $dbh = DBI->connect(
'DBI:mysql:database=test;host=127.0.0.1;port=3306',
'test', 'test',
{ RaiseError => 1, AutoCommit => 0 }
);


while( 1 ) {
 my $row;
 eval {
     my $sth = $dbh->prepare( "select id from job_queue where state='NEW' li
mit 1 for update" );
     $sth->execute();
     $row = $sth->fetchrow_hashref();
     $sth->finish();
 };

 if( $@ or !$row ) {
     # Couldn't lock or lock wait timeout
     $dbh->commit();
     sleep 10;
     next;
 }

 # Got one, change state, commit, and fork a worker

 $dbh->do( "update job_queue set state='WORKING' where id=" . $row->{id} );
 $dbh->commit();

 # Fork a worker
 if( fork )  {
     # Parent, let the child have the old connection and reconnect to
     # the db.
     $dbh->{InactiveDestroy} = 1;
     $dbh = DBI->connect(
        'DBI:mysql:database=test;host=127.0.0.1;port=3306',
        'test', 'test',
        { RaiseError => 1, AutoCommit => 0 }
     );
 } else{
     # fils
     print "Création du verrou\n";
     $dbh->do( "select * from job_queue where id=" . $row->{id}
         . " for update " );

     print "Travail en cours\n";
     #On simule un traitement
     srand( time );
     sleep rand 30;

     $dbh->do( "update job_queue set STATE='DONE' where
         id=" . $row->{id} );
     print "On Commit\n";
     $dbh->commit();

     $dbh->disconnect();

     exit;
 }
 sleep 1;
}

Maintenant, ce n'est pas mauvais. Mais il a au moins une chose que je n'aime pas. Si une des tâches n'aboutit pas, il n'existe aucun moyen pour réaffecter la tâche.

Ideally each worker would have a lock on his job row inside of a transaction, which it is doing now, but, instead of the job being in the 'WORKING' state first, I'd rather it was 'NEW' before the transaction started. If that were the case, if a worker died, it's unfinished transaction would be rolled back, and the job would be unlocked and NEW again.

Idéalement, chaque travailleur aurait un verrou sur son travail dans une transaction, ce qui est fait maintenant, mais, au lieu du travail en cours dans le «travail» d'abord, je préfère c'est "NOUVEAU" avant l'opération a commencé . Si tel était le cas, si un travailleur est mort, il est inachevé transaction serait annulée, et la tâche serait déverrouillé et NEW nouveau.

However, because of the way SELECT ... FOR UPDATE works, Innodb will deadlock waiting for 'NEW' jobs to unlock from the workers (or wait until they are done). This is sub-optimal, since my parent process could be forking new jobs in the meantime. What would fix this is a SELECT ... FOR UPDATE that skipped locked rows without blocking. Toutefois, en raison de la façon SELECT ... MISE À JOUR DE œuvres, InnoDB va impasse d'attente pour les «nouveaux» pour déverrouiller l'emploi des travailleurs (ou d'attendre jusqu'à ce qu'ils soient fait). Cela est sous-optimale, car mon père pourrait être forking de nouveaux emplois dans l'intervalle. Qu'est-ce que ce correctif est un SELECT ... MISE À JOUR DE verrouillé ignoré que des lignes sans blocage.

If anyone knows a good way to achieve this, please let me know!

However, assuming we have no better alternatives, we can create a reaper process like this:

[perl]
#!/usr/bin/perl

use DBD::mysql;

my $dbh = DBI->connect(
   'DBI:mysql:database=test;host=127.0.0.1;port=3306',
   'test', 'test',
   { RaiseError => 1, AutoCommit => 0 }
);


while( 1 ) {
    my $row;
    eval {
        my $sth = $dbh->prepare( "select id from job_queue where state='WORKING' limit 1 for update" );
        $sth->execute();
        $row = $sth->fetchrow_hashref();
        $sth->finish();
    };

    if( $@ or !$row ) {
        # Couldn't lock or lock wait timeout
        $dbh->commit();
        sleep 10;
        next;
    }

    # Got one, change state, commit, and fork a worker

    $dbh->do( "update job_queue set state='NEW' where id=" . $row->{id} );    $dbh->commit();

    sleep 1;
}

This will find the first WORKING row and try to lock it. Since normal jobs will be moved to DONE before they are committed, this should only ever find jobs that are in WORKING and unlocked, which means the worker died.

However this isn't perfect. Because SELECT ... FOR UPDATE will try to lock a row already locked, we have to wait until all jobs before our stalled job are complete, getting deadlocks along the way (be sure to set your innodb_lock_wait_timeout fairly low!. Even further, I've seen dead worker processes leave behind idle mysql connections that hold onto their row locks.

Is there any smoother way to do this? I'd love to hear other people's advice.

dimanche 8 février 2009

dim
08
fév '09

FOSDEM 2009 la room mysql est full

La room mySql rencontre un très beau succès. Entré a l'heure juste du début de la conférence de Kris, j'ai eu une des dernières places confortables.

La présentation concernait les solutions de monitoring. Je ne vais pas m'étendre sur le contenu. Il est plus simple d'aller voir ça sur le site.

Si j'écris c'est pour dire aussi combien il est plaisant de voir la qualité de ces présentations. Mais qu'en plus les liens entre elles sont établis. Les différents orateurs connaissent les présentations des autres.

lundi 12 janvier 2009

lun
12
jan '09

Mysql au Fosdem 2008 le dimanche 8 février.

  • Vladimir Kolesnikov: Practicing DBA's Guide to the PBXT Storage Engine
  • Kris Buytaert: Monitoring MySQL
  • Geert Vanderkelen: MySQL Cluster
  • Roland Bouman: MySQL 5.1 Plugins
  • Kaj Arnö: MySQL, powering and using Social Networks
  • Ewen Fortune: Percona MySQL patches and the XtraDB storage engine
  • Giuseppe Maxia: Boost performance with MySQL 5.1 partitions
  • Jurriaan Persyn: Database Sharding

Le programme détaillé est ici.

Ca se passe donc dans la Room AW1.126!

Moi je vais tenter d'aller voir Kris et Geert du Belgian Mysql User Group. Ainsi que Kaj Arnö et Giuseppe Maxia.

mardi 4 mars 2008

mar
04
mar '08

Belgian MySQL Users meeting

Le 18 mars prochain, au Cafe Sport à Leuven se tiendra le prochain meeting du MySQL Belgian User Group Geert envisage d'aborder les points suivants

  • revue rapide de ce qui s'est passé dernièrement avec MySQL
  • Avenir de certaines activités en Belgique
  • Bonnes pratiques, en ce qui concerne l'installation, l'assistance, et quelques points sur les performances.

c'est là que ca se passe.

mercredi 30 janvier 2008

mer
30
jan '08

Plus jamais de crash de réplication avec mySQL 5 ?

Version originale

par peter

Comme vous le savez peut-être, même si vous êtes seulement des tables Innodb votre réplication n'est pas complètement sécurisée en cas de crash. Si le slave MySQL se plante ou est éteint, il est probable que les logs de relais en court de synchronisation (ils ne sont pas synchronisés sur le disque), deviennent obsolète et la synchronisation est perdue

Pour les séries MySQL 4.0 et 4.1 série il y avait un gros hack si vous utilisez uniquement des tables Innodb.

La suite

jeudi 24 janvier 2008

jeu
24
jan '08

SELECT WEEK( 1er Janvier);

SELECT WEEK('2008-01-01'); // 0;
SELECT WEEK('2008-01-07'); // 1;
SELECT WEEK('2008-02-03'); // 4;
SELECT WEEK('2008-12-31'); // 52;

WEEK ne compte que les semaines complètes.

Certains vont dire, mais non la première semaine c'est la 0.

Non, non, en voici la preuve

SELECT DAYOFWEEK('2008-01-01'), # 3
       DAYOFWEEK('2007-01-01'), # 2
       DAYOFWEEK('2006-01-01'), # 1 <- ICI l'année commence le premier jour de la semaine
       DAYOFWEEK('2005-01-01'), # 7
       DAYOFWEEK('2004-01-01'), # 6
       DAYOFWEEK('2003-01-01'), # 5
       DAYOFWEEK('2002-01-01'); # 4 

Du coup

SELECT WEEK('2008-01-01'), # 0
       WEEK('2007-01-01'), # 0
       WEEK('2006-01-01'), # 1 <-on est bien la première semaine complète de l'année
       WEEK('2005-01-01'), # 0
       WEEK('2004-01-01'), # 0
       WEEK('2003-01-01'); # 0 

et du coup

SELECT WEEK('2008-12-31'), # 52
       WEEK('2007-12-31'), # 52
       WEEK('2006-12-31'), # 53
       WEEK('2005-12-31'), # 52
       WEEK('2004-12-31'), # 52
       WEEK('2003-12-31'); # 52 

En fait maintenant il y a une deuxième paramètre.

WEEK(date ,mode)

Voici ce que dit le manuel.

Avec deux arguments, la fonction WEEK() vous permet de spécifier si les semaines commencent le Dimanche ou le Lundi et la valeur retournée sera dans l'intervalle 0-53 ou bien 1-52. Lorsque l'argument mode est omis, la valeur de la variable default_week_format (ou 0 en MySQL 4.0 ou plus ancien) est utilisée.

Voici un tableau explicatif sur le fonctionnement du second argument : Valeur Signification

  • 0 : La semaine commence le Dimanche;l'intervalle de valeur de retour va de 0 à !2; la semaine 1 est la première semaine de l'année
  • 1 : La semaine commence le Lundi;l'intervalle de valeur de retour va de 0 à !2; la semaine 1 est la première semaine de l'année qui a plus de trois jours
  • 2 : La semaine commence le Dimanche;l'intervalle de valeur de retour va de 1 à !2; la semaine 1 est la première semaine de l'année
  • 3 : La semaine commence le Lundi;l'intervalle de valeur de retour va de 1 à !2; la semaine 1 est la première semaine de l'année qui a plus de trois jours
  • 4 : La semaine commence le Dimanche;l'intervalle de valeur de retour va de 0 à !2; la semaine 1 est la première semaine de l'année qui a plus de trois jours
  • 5 : La semaine commence le Lundi;l'intervalle de valeur de retour va de 0 à !2; la semaine 1 est la première semaine de l'année
  • 6 : La semaine commence le Dimanche;l'intervalle de valeur de retour va de 1 à !2; la semaine 1 est la première semaine de l'année qui a plus de trois jours
  • 7 : La semaine commence le Lundi;l'intervalle de valeur de retour va de 1 à !2; la semaine 1 est la première semaine de l'année

Le mode 3 est disponible depuis MySQL 4.0.5. Le mode 4 est disponible depuis MySQL 4.0.17.

Tags