2014-12-13 2 views
17

데이터베이스에서 행을 검색 한 다음 내용을 기반으로 작업을 수행하는 PHP 스크립트가 있습니다. 이 작업은 시간 소모적 일 수 있지만 (반드시 계산 비싼 것은 아닙니다) 여러 스크립트를 동시에 실행할 수 있어야합니다.PHP와 MySQL을 사용하여 간단한 대기열을 구현 하시겠습니까?

+---------------------+---------------+------+-----+---------------------+----------------+ 
| Field    | Type   | Null | Key | Default    | Extra   | 
+---------------------+---------------+------+-----+---------------------+----------------+ 
| id     | bigint(11) | NO | PRI | NULL    | auto_increment | 
..... 
| date_update_started | datetime  | NO |  | 0000-00-00 00:00:00 |    | 
| date_last_updated | datetime  | NO |  | 0000-00-00 00:00:00 |    | 
+---------------------+---------------+------+-----+---------------------+----------------+ 

내 스크립트가 현재 (작업이 완료되면 업데이트됩니다) 및 date_update_started를 사용하지 않는 date_last_updated에서 가장 오래된 날짜와 행을 선택 :

데이터베이스의 행은 다음과 같이 보인다.

스크립트의 여러 인스턴스를 지금 병렬로 실행하려면 동일한 행 (적어도 일부 시간)을 선택하면 중복 작업이 수행됩니다.

내가 생각하고있는 것은 트랜잭션을 사용하여 행을 선택하고 date_update_started 열을 업데이트 한 다음 date_update_started보다 큰 행을 선택하는 행을 선택하는 SQL 문에 WHERE 조건을 추가하는 것입니다. 다른 스크립트가 작동하지 않는지 확인하십시오). 예 :

$sth = $dbh->prepare(' 
    START TRANSACTION; 
    SELECT * FROM table WHERE date_update_started > 1 DAY ORDER BY date_last_updated LIMIT 1000; 
    UPDATE table DAY SET date_update_started = UTC_TIMESTAMP() WHERE id IN (SELECT id FROM table WHERE date_update_started > 1 DAY ORDER BY date_last_updated LIMIT 1000;); 
    COMMIT; 
'); 
$sth->execute(); // in real code some values will be bound 
$rows = $sth->fetchAll(PDO::FETCH_ASSOC); 

필자가 읽은 바로는 이것은 본질적으로 대기열 구현이며 MySQL에서 눈살을 찌푸린 것처럼 보입니다. 마찬가지로, 여러 스크립트를 병렬로 실행할 수있는 방법을 찾아야하며이 작업을 수행 한 후에 필자가 생각해 냈습니다.

이러한 접근 방식이 효과가 있습니까? 더 좋은 방법이 있습니까?

+0

어떻게 병렬 스크립트를 실행합니까? – Lupin

+0

@Lupin 현재 스크립트는 cron 작업을 통해 매 15 분마다 실행 중입니다. 스크립트는 다른 인스턴스가 실행 중인지 확인한 후 종료됩니다. 아직 여러 스크립트를 관리하는 방법을 모르겠습니다. 데이터베이스에 카운터가있어 인스턴스의 수를 제한하고 인스턴스 수를 제한 할 수 있지만 한 번에 하나의 문제가 발생할 수 있습니다. – Nate

+0

OK , 나를 완전히 이해할 수있는 몇 가지 추가 질문 : 1. 행을 선택하여 작업하고 DB로 다시 업데이트하는 스크립트가 있습니까? 2. 병렬 스크립트를 실행하고 다른 행에서 동일한 작업을 수행하고 싶습니다. 3. 스크립트가 실행될 때마다 선택한 행이 연속적입니까? 즉 1-100, 101-200 등입니까? 아니면 id가 임의로 지정되어 있으며 date_update_started가 1보다 큰 행만 선택됩니까? – Lupin

답변

5

나는 당신이 선택한 행에 일종의 식별자를 추가하는 한 당신의 접근 방식이 효과가 있다고 생각한다. @JuniusRendel이 제안되었고 나는 다른 문자열을 사용하는 것에 대해서 생각할 수도있다. 스크립트가 오류를 일으키고 정상적으로 완료되지 않은 경우 키 (임의 또는 인스턴스 ID). 작업 후에 행을 다시 업데이트 한 후에 이러한 필드를 정리해야합니다.

이 방법의 문제점은 동일한 지점에서 실행되는 두 개의 스크립트가 잠겨져서 서명되기 전에 동일한 행을 선택할 수있는 옵션이라는 것입니다. 여기에 내가 볼 수 있듯이, 실제로 두 가지 스크립트의 최종 결과가 같으면 행에 어떤 작업을하는지에 따라 달라집니다. 문제는 낭비되는 시간과 서버 메모리에 대한 것입니다. 작은 문제는 아니지만 지금은 제쳐두고 ...). 작업 결과가 두 스크립트에서 서로 다른 업데이트를 발생시키는 경우 TB의 끝에 잘못된 업데이트를 가질 수있는 문제가 발생합니다.

@Jean은 MySql 잠금을 사용하는 것과 관련된 두 번째 접근법에 대해 언급했습니다. 나는 주제에 대한 전문가가 아니지만 좋은 접근 방법 인 것처럼 보이며 'Select .... FOR UPDATE'문을 사용하면 동일한 전화에서 할 수있는 것처럼 당신이 찾고있는 것을 줄 수 있습니다. & 선택 업데이트 - 2보다 빠릅니다. 쿼리를 분리하고 다른 인스턴스가 잠길 때이 행을 선택하는 위험을 줄일 수 있습니다.

'SELECT .... UPDATE에 대한'명세서의 모습 수 있도록, 당신은 선택 문을 실행하고 업데이트를위한 그 특정 행을 잠글 수 있습니다 :

START TRANSACTION; 
    SELECT * FROM tb where field='value' LIMIT 1000 FOR UPDATE; 
    UPDATE tb SET lock_field='1' WHERE field='value' LIMIT 1000; 
COMMIT; 

잠금은 강력하지만, 다른 섹션에서 응용 프로그램에 영향을 미치지 않도록주의하십시오. 업데이트를 위해 현재 잠겨있는 선택된 행이 응용 프로그램의 다른 곳 (최종 사용자의 경우)에 요청되었는지 그리고 그 경우 어떤 일이 발생하는지 확인하십시오.

또한 테이블은 InnoDB 여야하며 where 절을 검사하는 필드에는 Mysql 인덱스가있는 것이 좋으며 그렇지 않으면 전체 테이블을 잠그거나 'Gap Lock'이 발생하는 것이 좋습니다.

잠금 프로세스가 특히 병렬 스크립트를 실행할 때 CPU & 메모리에 많은 영향을 줄 수 있습니다. 이 도움이, 당신이 진행하는 방법을 듣고 싶습니다 http://www.percona.com/blog/2006/08/06/select-lock-in-share-mode-and-for-update/

희망 : 여기

주제에 또 다른 읽기입니다.

1

편집 : 죄송합니다, 나는 완전히 오해 질문

스크립트가 작업하는 항목에 값을 true로 놓고, 당신이 일을 끝낼 때 넣어 당신은 당신의 테이블에 "고정"열을 넣어해야

그것은 거짓이다.

내 경우에는 다른 세 개의 타임 스탬프 (정수) 열을 넣었습니다 : target_ts, start_ts, done_ts. 당신은

UPDATE table SET locked = TRUE WHERE target_ts<=UNIX_TIMESTAMP() AND ISNULL(done_ts) AND ISNULL(start_ts); 

다음

SELECT * FROM table WHERE target_ts<=UNIX_TIMESTAMP() AND ISNULL(start_ts) AND locked=TRUE; 

가 현재 타임 스탬프에 done_ts 속성을 설정하여 작업을 수행하고 각 항목 하나 (피하기 위해 데이터 inconcistencies) 하나를 업데이트 (당신은 또한 지금의 잠금을 해제 할 수 있습니다). target_ts를 다음 업데이트로 업데이트하거나이 열을 무시하고 선택한 항목에 대해 done_ts를 사용할 수 있습니다.

+0

PHP는 실제로 멀티 스레딩을 지원하지 않는다고 생각합니다. 그러나 어떤 경우에는 스크립트의 여러 인스턴스를 실행하는 것이 문제가 아닙니다. 질문은 주로 DB에서 행을 검색하는 방법을 다루는 것입니다. – Nate

+0

업데이트 됨, 미안, 아마도 술에 취해서. :) 스레드에 대해서는 PECL 확장이 주장하는 것입니다. 그러나 테스트하지 않았습니다 ... – n00dl3

4

우리는이 기능을 프로덕션 환경에서 구현했습니다.

UPDATE queue SET id = LAST_INSERT_ID(id), date_update_started = ... 
WHERE date_update_started IS NULL AND ... 
LIMIT 1; 

우리는 단일 트랜잭션에서이 업데이트를 수행하고 우리는 LAST_INSERT_ID 기능을 활용 : 중복을 방지하기 위해

, 우리는이 같은 MySQL의 UPDATE (나는 테이블을 닮은 쿼리를 수정) 할. 이와 같이 매개 변수를 사용하면 트랜잭션 세션에 업데이트 된 단일 (LIMIT 1) 큐 (있는 경우)의 ID 인 트랜잭션 인스턴스에 매개 변수를 씁니다.

그냥 그 후, 우리는 수행

매개 변수없이 사용
SELECT LAST_INSERT_ID(); 

, 그것은 수행 할 수있는 큐 항목의 ID를 획득, 이전에 저장된 값을 검색합니다.

+0

"쓰기 잠금"이 무엇을 의미하는지 자세히 설명해 주시겠습니까? 코드 예제가 있을까요? – Nate

+0

@ 네이트, 편집 및 확장;) 또한 RabbitMQ, BTW를 사용하는 것이 좋습니다. 우리는 그것을 사용하는 꿈을 꾸고있다 : D – Jean

1

스크립트가 실행될 때마다 스크립트에 uniqid가 생성됩니다.

$sctiptInstance = uniqid(); 

은 내가 VARCHAR로이 값을 유지하고 그 위에 인덱스를 넣어 스크립트 인스턴스 열을 추가합니다. 스크립트가 실행될 때 스크립트 내에서 행을 제외하고 모든 논리에 따라 행을 선택하고 스크립트 인스턴스로 해당 행을 업데이트하기 위해 트랜잭션 내부의 업데이트에 select를 사용합니다.뭔가 같은 :

START TRANSACTION; 
SELECT * FROM table WHERE script_instance = '' AND date_update_started > 1 DAY ORDER BY date_last_updated LIMIT 1000 FOR UPDATE; 
UPDATE table SET date_update_started = UTC_TIMESTAMP(), script_instance = '{$scriptInstance}' WHERE script_instance = '' AND date_update_started > 1 DAY ORDER BY date_last_updated LIMIT 1000; 
COMMIT; 

이제 이러한 행은 스크립트의 다른 인스턴스에서 제외됩니다. 작업을 수행 한 다음 행을 업데이트하여 스크립트 인스턴스를 null 또는 공백으로 다시 설정하고 마지막으로 업데이트 된 날짜를 업데이트하십시오.

"현재 인스턴스"또는 이와 비슷한 다른 테이블에 스크립트 인스턴스를 작성하고 해당 스크립트 테이블에서 동시 스크립트 수를 제어하는 ​​실행 스크립트 수를 확인하도록 할 수도 있습니다. 테이블의 스크립트 PID도 추가합니다. 그런 다음이 정보를 사용하여 cron에서 정기적으로 실행하여 장기 실행 또는 불량 프로세스를 확인하고 죽이는 등의 임시 작업 스크립트를 만들 수 있습니다.

1

나는 프로덕션 환경에서 이와 같은 시스템을 사용하고 있습니다. 우리는 매분마다 스크립트를 실행하여 일부 처리를 수행하며 때로는 실행에 1 분 이상 걸릴 수 있습니다.

우리는 상태에 대한 테이블 열을 가지고 있는데, NOT RUN YET에 대해 1, FINISHED에 대해 1, 언더 웨이에 대해 다른 값이 있습니다.

스크립트가 수행하는 첫 번째 작업은 해당 행에서 작업중인 것을 의미하는 값으로 한 줄 또는 여러 줄을 설정하여 테이블을 업데이트하는 것입니다. getmypid()을 사용하여 작업하려는 줄을 업데이트하고 아직 처리되지 않은 줄을 업데이트합니다.

처리가 끝나면 스크립트는 프로세스 ID가 동일한 행을 업데이트하여 완료 됨 (상태 1)으로 표시합니다.

이 방법을 사용하면 각 스크립트가 이미 처리중인 행을 처리하지 않고 매력적으로 작동합니다. 이것은 더 좋은 방법이 없다는 것을 의미하지는 않지만 이것은 일을 끝내게합니다.

1

필자는 과거와 매우 유사한 이유로 저장 프로 시저를 사용했습니다. 향후 선택에서 해당 항목을 제거하기 위해 선택된 플래그가 업데이트 된 동안 FOR UPDATE 읽기 잠금을 사용하여 테이블을 잠급니다. 그것은 다음과 같이 보입니다 :

CREATE PROCEDURE `select_and_lock`() 
BEGIN 
    START TRANSACTION; 
    SELECT your_fields FROM a_table WHERE some_stuff=something 
    AND selected = 0 FOR UPDATE; 
    UPDATE a_table SET selected = 1; 
    COMMIT; 
END$$ 

저장 프로 시저에서 수행해야하는 이유는 없지만 지금 생각해보십시오.

관련 문제