web-dev-qa-db-fra.com

Comment verrouiller la lecture / écriture sur les tables MySQL pour pouvoir sélectionner puis insérer sans autres programmes lire / écrire dans la base de données?

J'exécute plusieurs instances d'un webcrawler en parallèle.

Chaque robot sélectionne un domaine dans une table, insère cette URL et une heure de début dans une table de journal, puis commence à analyser le domaine.

D'autres robots parallèles vérifient la table des journaux pour voir quels domaines sont déjà analysés avant de sélectionner leur propre domaine à analyser.

Je dois empêcher d'autres robots d'exploration de sélectionner un domaine qui vient d'être sélectionné par un autre robot d'exploration mais qui n'a pas encore d'entrée de journal. Ma meilleure estimation de la façon de procéder consiste à verrouiller la base de données de toutes les autres lectures/écritures pendant qu'un robot sélectionne un domaine et insère une ligne dans la table des journaux (deux requêtes).

Comment diable fait-on cela? J'ai bien peur que cela soit terriblement complexe et repose sur bien d'autres choses. Aidez-moi à démarrer.


Ce code semble être une bonne solution (voir l'erreur ci-dessous, cependant):

INSERT INTO crawlLog (companyId, timeStartCrawling)
VALUES
(
    (
        SELECT companies.id FROM companies
        LEFT OUTER JOIN crawlLog
        ON companies.id = crawlLog.companyId
        WHERE crawlLog.companyId IS NULL
        LIMIT 1
    ),
    now()
)

mais je reçois toujours l'erreur mysql suivante:

You can't specify target table 'crawlLog' for update in FROM clause

Existe-t-il un moyen d'accomplir la même chose sans ce problème? J'ai essayé deux façons différentes. Y compris ceci:

INSERT INTO crawlLog (companyId, timeStartCrawling)
VALUES
(
    (
        SELECT id
        FROM companies
        WHERE id NOT IN (SELECT companyId FROM crawlLog) LIMIT 1
    ),
    now()
)
25
T. Brian Jones

Je me suis inspiré de la réponse de @ Eljakim et j'ai commencé ce nouveau fil où j'ai trouvé un bon truc. Cela n'implique aucun verrouillage et est très simple.

INSERT INTO crawlLog (companyId, timeStartCrawling)
SELECT id, now()
FROM companies
WHERE id NOT IN
(
    SELECT companyId
    FROM crawlLog AS crawlLogAlias
)
LIMIT 1
2
T. Brian Jones

Vous pouvez verrouiller des tables à l'aide de MySQL LOCK TABLES commande comme ceci:

LOCK TABLES tablename WRITE;

# Do other queries here

UNLOCK TABLES;

Voir:

http://dev.mysql.com/doc/refman/5.5/en/lock-tables.html

46
qbert220

Vous ne voulez probablement pas verrouiller la table. Si vous faites cela, vous devrez vous soucier des erreurs de capture lorsque les autres robots tentent d'écrire dans la base de données - c'est ce que vous pensiez lorsque vous avez dit "... terriblement complexe et repose sur beaucoup d'autres choses".

Au lieu de cela, vous devriez probablement envelopper le groupe de requêtes dans une transaction MySQL (voir http://dev.mysql.com/doc/refman/5.0/en/commit.html ) comme ceci:

START TRANSACTION;
SELECT @URL:=url FROM tablewiththeurls WHERE uncrawled=1 ORDER BY somecriterion LIMIT 1;
INSERT INTO loggingtable SET url=@URL;
COMMIT;

Ou quelque chose proche de cela.

[modifier] Je viens de réaliser - vous pourriez probablement faire tout ce dont vous avez besoin en une seule requête et ne pas avoir à vous soucier des transactions. Quelque chose comme ça:

INSERT INTO loggingtable (url) SELECT url FROM tablewithurls u LEFT JOIN loggingtable l ON l.url=t.url WHERE {some criterion used to pick the url to work on} AND l.url IS NULL.
4
ratsbane

Eh bien, les verrous de table sont une façon de gérer cela; mais cela rend les demandes parallèles impossibles. Si la table est InnoDB, vous pouvez forcer un verrouillage de ligne à la place, en utilisant SELECT ... FOR UPDATE dans une transaction.

BEGIN;

SELECT ... FROM your_table WHERE domainname = ... FOR UPDATE

# do whatever you have to do

COMMIT;

Veuillez noter que vous aurez besoin d'un index sur domainname (ou dans la colonne que vous utilisez dans la clause WHERE) pour que cela fonctionne, mais cela a du sens en général et je suppose que vous l'avez de toute façon.

3
wonk0

Je n'utiliserais pas de verrouillage ou de transactions.

La méthode la plus simple consiste à INSÉRER un enregistrement dans la table de journalisation s'il n'est pas encore présent, puis à rechercher cet enregistrement.

Supposons que vous ayez tblcrawels (cra_id) qui est remplie avec vos robots et tblurl (url_id) qui est remplie avec les URL, et une table tbllogging (log_cra_id, log_url_id) pour votre fichier journal.

Vous exécuteriez la requête suivante si le robot 1 veut démarrer l'analyse de l'url 2:

INSERT INTO tbllogging (log_cra_id, log_url_id) 
SELECT 1, url_id FROM tblurl LEFT JOIN tbllogging on url_id=log_url 
WHERE url_id=2 AND log_url_id IS NULL;

L'étape suivante consiste à vérifier si cet enregistrement a été inséré.

SELECT * FROM tbllogging WHERE log_url_id=2 AND log_cra_id=1

Si vous obtenez des résultats, le robot 1 peut explorer cette URL. Si vous n'obtenez aucun résultat, cela signifie qu'un autre robot a été inséré dans la même ligne et est déjà en train d'analyser.

3
Eljakim