tables - ¿Cómo bloqueo la lectura/escritura en las tablas MySQL para que pueda seleccionar y luego insertar sin que otros programas lean/escriban en la base de datos?
tipos de bloqueo base de datos (5)
Estoy ejecutando muchas instancias de un webcrawler en paralelo.
Cada rastreador selecciona un dominio de una tabla, inserta esa url y una hora de inicio en una tabla de registro y luego comienza a rastrear el dominio.
Otros rastreadores paralelos verifican la tabla de registro para ver qué dominios ya se están rastreando antes de seleccionar su propio dominio para rastrear.
Necesito evitar que otros rastreadores seleccionen un dominio que otro rastreador haya seleccionado pero que aún no tenga una entrada de registro. Mi mejor estimación de cómo hacer esto es bloquear la base de datos de todas las demás lecturas / escrituras mientras un rastreador selecciona un dominio e inserta una fila en la tabla de registro (dos consultas).
¿Cómo diablos se hace esto? Me temo que esto es terriblemente complejo y se basa en muchas otras cosas. Por favor ayúdame a comenzar.
Este código parece una buena solución (vea el error a continuación, sin embargo):
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()
)
pero sigo recibiendo el siguiente error mysql:
You can''t specify target table ''crawlLog'' for update in FROM clause
¿Hay una manera de lograr lo mismo sin este problema? He intentado un par de maneras diferentes. Incluyendo esto:
INSERT INTO crawlLog (companyId, timeStartCrawling)
VALUES
(
(
SELECT id
FROM companies
WHERE id NOT IN (SELECT companyId FROM crawlLog) LIMIT 1
),
now()
)
Bueno, las cerraduras de mesa son una forma de lidiar con eso; pero esto hace que las solicitudes paralelas sean imposibles. Si la tabla es InnoDB, podría forzar un bloqueo de fila en su lugar, utilizando SELECT ... FOR UPDATE dentro de una transacción.
BEGIN;
SELECT ... FROM your_table WHERE domainname = ... FOR UPDATE
# do whatever you have to do
COMMIT;
Tenga en cuenta que necesitará un índice en el domainname
de domainname
(o la columna que use en la cláusula WHERE) para que esto funcione, pero esto tiene sentido en general y supongo que lo tendrá de todos modos.
Me inspiré en la respuesta de @ Eljakim y comencé este nuevo hilo en el que descubrí un gran truco. No implica bloquear nada y es muy sencillo.
INSERT INTO crawlLog (companyId, timeStartCrawling)
SELECT id, now()
FROM companies
WHERE id NOT IN
(
SELECT companyId
FROM crawlLog AS crawlLogAlias
)
LIMIT 1
Probablemente no quieras cerrar la mesa. Si lo hace, tendrá que preocuparse por los errores de captura cuando los otros rastreadores intenten escribir en la base de datos, que es lo que estaba pensando cuando dijo "... terriblemente complejo y se basa en muchas otras cosas".
En su lugar, probablemente debería envolver el grupo de consultas en una transacción de MySQL (consulte http://dev.mysql.com/doc/refman/5.0/en/commit.html ) de esta manera:
START TRANSACTION;
SELECT @URL:=url FROM tablewiththeurls WHERE uncrawled=1 ORDER BY somecriterion LIMIT 1;
INSERT INTO loggingtable SET url=@URL;
COMMIT;
O algo parecido a eso.
[editar] Me di cuenta de que es probable que puedas hacer todo lo que necesites en una sola consulta y ni siquiera tengas que preocuparte por las transacciones. Algo como esto:
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.
Puede bloquear tablas usando el comando LOCK TABLES
esta manera:
LOCK TABLES tablename WRITE;
# Do other queries here
UNLOCK TABLES;
Ver:
Yo no usaría el bloqueo, o las transacciones.
La forma más fácil de hacerlo es INSERTAR un registro en la tabla de registro si aún no está presente, y luego verificar ese registro.
Suponga que tiene tblcrawels (cra_id)
que se llena con sus rastreadores y tblurl (url_id)
que se llena con las URL, y una tabla tbllogging (log_cra_id, log_url_id)
para su archivo de registro.
Ejecutaría la siguiente consulta si el rastreador 1 quiere comenzar a rastrear la 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;
El siguiente paso es verificar si este registro ha sido insertado.
SELECT * FROM tbllogging WHERE log_url_id=2 AND log_cra_id=1
Si obtiene algún resultado, el rastreador 1 puede rastrear esta URL. Si no obtiene ningún resultado, significa que otro rastreador se insertó en la misma línea y ya está rastreando.