Как заблокировать чтение / запись в таблицы MySQL, чтобы я мог выбрать и затем вставить без чтения/записи других программ в базу данных?

я запускаю много экземпляров webcrawler параллельно.

каждый искатель выбирает домен из таблицы, вставляет этот url и время начала в таблицу журнала, а затем начинает обход домена.

другие параллельные обходчики проверьте таблицу журнала, чтобы увидеть, какие домены уже обходятся, прежде чем выбрать свой собственный домен для обхода.

мне нужно запретить другим искателям выбирать домен, который только что был выбран другим искателем, но еще нет записи в журнале. Мое лучшее предположение о том, как это сделать, - заблокировать базу данных от всех других операций чтения/записи, пока один искатель выбирает домен и вставляет строку в таблицу журнала (два запроса).

как же это делается? Я боюсь, что это ужасно сложно и зависит от многих других вещей. Пожалуйста, помогите мне.


этот код кажется хорошим решением (см. ошибку ниже, однако):

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()
)

но я продолжаю получать следующая ошибка mysql:

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

есть ли способ сделать то же самое без этой проблемы? Я пробовал несколько разных способов. В том числе:

INSERT INTO crawlLog (companyId, timeStartCrawling)
VALUES
(
    (
        SELECT id
        FROM companies
        WHERE id NOT IN (SELECT companyId FROM crawlLog) LIMIT 1
    ),
    now()
)

5 ответов


вы можете заблокировать таблицы с помощью MySQL такой:

LOCK TABLES tablename WRITE;

# Do other queries here

UNLOCK TABLES;

посмотреть:

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


вы, вероятно, не хотите, чтобы заблокировать таблицу. Если вы это сделаете, вам придется беспокоиться об ошибках захвата, когда другие искатели пытаются писать в базу данных - о чем вы думали, когда говорили "...ужасно сложный и опирается на многое другое."

вместо этого вы, вероятно, должны обернуть группу запросов в транзакцию MySQL (см. http://dev.mysql.com/doc/refman/5.0/en/commit.html) Вот так:

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

или что-то близкое к что.

[edit] я только что понял - вы, вероятно, можете сделать все, что вам нужно, в одном запросе и даже не беспокоиться о транзакциях. Что-то вроде этого:--3-->

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.

Я бы не использовал блокировку или транзакции.

самый простой способ-вставить запись в таблицу регистрации, если она еще не присутствует, а затем проверить эту запись.

Предположим, у вас есть tblcrawels (cra_id) который заполнен вашими искателями и tblurl (url_id) который заполнен URL-адресами и таблицей tbllogging (log_cra_id, log_url_id) для вашего журнала.

вы должны выполнить следующий запрос, если искатель 1 хочет начать обход 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;

следующий шаг-проверить была ли эта запись вставлена.

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

если вы получите какие-либо результаты, то crawler 1 может сканировать этот url. Если вы не получите никаких результатов, это означает, что другой искатель вставлен в ту же строку и уже выполняет обход.


ну, блокировки таблиц-один из способов справиться с этим; но это делает параллельные запросы невозможными. Если таблица InnoDB, вы можете принудительно заблокировать строку, используя выбрать ... ДЛЯ ОБНОВЛЕНИЯ в рамках транзакции.

BEGIN;

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

# do whatever you have to do

COMMIT;

обратите внимание, что вам понадобится указатель на domainname (или любой столбец, который вы используете в предложении WHERE) для этого, но это имеет смысл в целом, и я предполагаю, что у вас все равно будет это.


Я получил некоторое вдохновение от ответа @Eljakim и начал этот новый поток где я придумал трюк. Он не включает в себя блокировку чего-либо и очень прост.

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