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

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

Каждый сканер выбирает домен из таблицы, вставляет этот 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 ]

46 голосов
/ 08 июля 2011

Вы можете заблокировать таблицы с помощью команды MySQL LOCK TABLES следующим образом:

LOCK TABLES tablename WRITE;

# Do other queries here

UNLOCK TABLES;

См:

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

4 голосов
/ 08 июля 2011

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

Вместо этого вам, вероятно, следует заключить группу запросов в транзакцию 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;

Или что-то близкое к этому.

[править] Я только что понял - вы, вероятно, можете сделать все, что вам нужно, в одном запросе, и вам даже не придется беспокоиться о транзакциях. Примерно так:

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.
3 голосов
/ 08 июля 2011

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

Самый простой способ - это ВСТАВИТЬ запись в таблицу журналов, если она еще не существует, а затем проверить эту запись.

Предположим, у вас есть 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

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

3 голосов
/ 08 июля 2011

Ну, блокировка таблиц - один из способов справиться с этим; но это делает параллельные запросы невозможными. Если таблица InnoDB, вы можете вместо этого принудительно заблокировать строку, используя SELECT ... FOR UPDATE в транзакции.

BEGIN;

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

# do whatever you have to do

COMMIT;

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

2 голосов
/ 09 июля 2011

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

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