2016-10-28 2 views
1

나는 약 1 주일 동안 다양한 접근 방식을 사용하여 테스트를하는 동안 엄청난 부하로 인해 서버를 항상 "충돌"시킵니다.간단한 업데이트/선택 (큰 테이블, 많은 연결)에서 Mysql 테이블 성능 문제가 발생했습니다.

mysql> explain select id FROM task_jobs FORCE INDEX (index_update_get_work) WHERE customer_job_id=31 AND client_reserved=0 AND result_delivered=0 AND (assigned_instance is NULL) LIMIT 10; 
+----+-------------+--------------------+------------+------+-----------------------+-----------------------+---------+-------------------------+--------+----------+-------------+ 
| id | select_type | table    | partitions | type | possible_keys   | key     | key_len | ref      | rows | filtered | Extra  | 
+----+-------------+--------------------+------------+------+-----------------------+-----------------------+---------+-------------------------+--------+----------+-------------+ 
| 1 | SIMPLE  | task_jobs | NULL  | ref | index_update_get_work | index_update_get_work | 14  | const,const,const,const | 104226 | 100.00 | Using where | 
+----+-------------+--------------------+------------+------+-----------------------+-----------------------+---------+-------------------------+--------+----------+-------------+ 
1 row in set, 1 warning (0.00 sec) 

일반형 유지 : 수백만 개의 행이 늘어나는 테이블이 있습니다.
이 테이블은 한 번에 수천 개의 클라우드 인스턴스에 수백 개의 작업 작업을 제공합니다.

모든 인스턴스는 내 테이블 (최대 3000 개의 쿼리)을 쿼리하고 작업 데이터를 수신하도록 요청합니다.

"열린 작업"으로 수십만 개의 행이 있지만 한 번에 한 인스턴스에 10-20 개만 전달됩니다. 가장 성능이 좋은 여전히 ​​큰 문제

내 현재의 접근 방식 :

  1. 내가 LIMIT 10 행에 UPDATE을 WHERE customer_job_id = 31 AND (NULL assigned_instance이) = 0을 client_reserved AND = 0 result_delivered AND
    쿼리가 자체 설명하는 것 같아요, 아직 특정 "작업 ID"에서 결과를 배달하지 않은 할당되지 않은 작업을 찾습니다.
    쿼리는 SELECT 대신 UPDATE로 시작됩니다.

  2. 이제 프로그램 로직은 delievered 행의 일부를 선택하고 마지막으로 (X, X, X,을,을 X X)

내가 IN WHERE ID를 사용하여 인스턴스에 할당하는 두 번째 업데이트한다 이 접근 방식을 사용하여 바쁜 것으로 업데이트하여 10 개의 행을 빠르게 "잠글"수 있으므로 다음 인스턴스도 다른 10 개의 행을 잠글 수 있습니다.

이 작업은 정상적으로 작동하며 한 번에 100 개 인스턴스에 문제가 없는데 500 개 인스턴스로로드하면 서버가 잠긴다. 데이터베이스 연결을 수백 개의 LOCK 된 요청으로 채워서 15 초 (최적화 전 140 초) 동안 10 개의 행을 업데이트합니다.

처음에 볼 수 있듯이 SELECT (실제로는 UPDATE SET client_reserved = 1, assigned_instance = $ instance_id)는 100k 행 (아마도 그 이상)을 거쳐야합니다. 단지 10 명이 선택 되더라도 작업이 완료되고 처음 10 개가 업데이트되기 전에 모든 작업을 검토하는 것으로 보입니다. 적어도 EXPLAIN이이를 알려줍니다.

기본적으로 제 질문은 더 나은 접근 방법을 찾는 것입니다.
나는 수천 개의 별개의 연결로부터 수초 내에 수천 개의 행을 채울 필요가있다.
"customer_job_id = 31 AND client_reserved = 0 AND result_delivered = 0 AND (assigned_instance가 NULL)"인 100-500k 개의 작업/행 중 적은 수의 행을 가져올 필요가있을 때마다.

assigned_instance는 varchar (NULL 조건의 경우 인덱스가 1)이며 나머지는 tinyint (1)입니다. 나는 그들 모두를 결합하는 색인을 만들었지 만 그것은 정말로 도움이되지 못했다.

업데이트에 대한 명확한
: 다른 명의 동시 요청이있는 경우 주 서버의 API가 모르기 때문에 나는 "UPDATE"를 사용하고
"나에게 일을주는".
그래서 현재 인스턴스에 대해 "예약"하기 위해 여러 행에서 UPDATE를 사용했습니다.
UPDATE가 SQL의 "원자"연산이므로 다른 요청이 동일한 작업 (경쟁 조건)과 함께 제공 될 위험이 없습니다.

업데이트 질문
일반적인 질문 : 나는 10이 충분히있는 경우 10
은 왜 10 만 개 결과를 검색 않습니다 LIMIT 사용할 수 있습니까? ORDER BY RAND()를 추가하면 실제로 100k 결과를 모두 살펴보고 순서를 바꾸어야합니다 (동일한 성능 비용). mysql이 10 개의 히트를 발견하면 멈추지 않는 이유는 무엇입니까? (LIMIT 10과 ORDER BY 절을 생략하여 바랬습니다)

+0

LIMIT에 대한 일반적인 질문 : MySQL은 쿼리를 만족하는 모든 행을 검색 한 다음 OFFSET에서 시작하여 LIMIT 개의 행을 제외한 모든 것을 버립니다. 왜 더 잘 설계되지 않았습니까?그것은 불가능하기 때문에, MySQL은 행을 수집하고, 순서를 적용해야하며 (이 시점에서 쿼리를 만족시키는 모든 것을 수집했다.) 주문이 적용된 후에 만, 처음부터 시작하여 10 개의 행을 반환 할 수있다. LIMIT는 성능을 향상시키지 않고 단순히 소켓에 기록 된 레코드 수를 줄입니다. –

+0

@ N.B. 나는 주문할 때 이해하지만 모든 주문을 빠뜨리려고했습니다. 그런 경우에는 mysql이 순서를 무시하고 일단 LIMIT가 실행되면 작업을 중지 할 수 있습니다. 아니 ? – John

+1

아닙니다. order by 절을 지정하지 않더라도 반환 레코드를 찾아야하는 순서가 있습니다. 이 주문이 기본 키 (디스크에 기록 된 방식) 인 경우 10 개의 레코드 및 정지 만 찾을 수 있습니다. 실제로 그렇게 할 수 있는지 확신 할 수 없기 때문에 (나는 그런 것을 개발하는 것이 중요 함을 알고 있습니다.) 실제로 그것을하고 있다고 주장하지는 않을 것입니다. 그러나 저는 여러분이 어디에서 왔는지 알고 있습니다. 우리는 모두 그런 식으로 행동하는 경우에 그것을 좋아하십시오 :) –

답변

1

좋아요, 그래서 당신에게 필요한 것은 작업 큐입니다.

LOCK TABLES task_queue READ; 
SELECT * FROM task_queue LIMIT x; 
DELETE FROM task_queue LIMIT x; 
UNLOCK TABLES; 
: 큐에서 "팝"될 수있는 가능한 작업은,

작업 대기열

CREATE TABLE task_queue (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 
    task_job_id INT UNSIGNED NOT NULL 
) ENGINE=InnoDB; 

당신은 매우 빠르게 "팝업"이 같은으로 테이블에서 항목을 X 수

당신은 당신의 기준에 일치하는 새 작업을 검색하고 대기열에 매 순간을 실행하는 크론으로, 큐의 끝 부분에 계속해서 쓸 수있다 : 여기

SELECT id 
FROM task_jobs 
FORCE INDEX (index_update_get_work) 
WHERE customer_job_id = 31 
    AND client_reserved = 0 
    AND result_delivered = 0 
    AND assigned_instance IS NULL 
    AND id NOT IN (SELECT task_job_id FROM task_queue); 

단일 표 것은 대한 생각 단일 테이블 대기열 디자인 :

DB에 대기열 색인을 유지하려고합니다. 단일 행 테이블로이 작업을 수행 할 수 있습니다.

CREATE TABLE queue_index (
    id INT UNSIGNED NOT NULL PRIMARY KEY 
) ENGINE=InnoDB; 

최소 ID로 한 번 초기화하십시오.

INSERT INTO queue_index (id) 
SELECT MIN(id) FROM task_jobs 
WHERE customer_job_id = 31 
    AND client_reserved = 0 
    AND result_delivered = 0 
    AND assigned_instance IS NULL; 

이 같은 뭔가 테이블에서 X 항목을 "팝업"수 :

BEGIN TRANSACTION; 

SELECT @oldid = id FROM queue_index; 

SELECT @newid = MAX(id) 
FROM (
    SELECT id 
    FROM task_jobs 
    WHERE id > @oldid 
    LIMIT x 
) AS j; 

UPDATE queue_index SET id = @newid; 

SELECT * FROM task_queue WHERE id <= @newid; 

END TRANSACTION; 

그런 다음, 추가 된 새로운 task_jobs가 자동으로 지정을 위해 인 - 큐 될 것입니다.

포인터가 주어진 작업 범위를 벗어난 경우 할당을 다시 지정하려면 해당 레코드를 삭제하고 목록의 끝에서 다시 삽입해야합니다.

+0

응답을 보내 주셔서 감사합니다! 그런데 어떻게하면 1000 개의 요청이 같은 초에 들어 오게 되는가? "선택"을 사용하면 비슷한 행이 대략 1000 개씩 생깁니다. 동시성 요청이 동일한 행을 수신하는 것이 불가능하기 때문에 UPDATE를 사용했습니다. 문제는 각 UPDATE가 한 번에 너무 많은 행을 고려한다는 것입니다. 들어오는 "나에게 일하는"요청을 수신 대기하는 API는 다른 모든 인스턴스에 대해서도 알지 못하므로 작업을 요청합니다. 같은 작업을 두 번 줄 위험없이 몇 줄을 얻으려면 "원자"접근 방식이 필요했습니다. – John

+1

알았어. 나는 그 문제를 오해했다. 더 깊이 생각해보고 아이디어가 있는지 알려 드리겠습니다. –

+0

나는 문제가 너무 깊어서 설명하기가 어렵다;) 어쨌든 너는 내가 다른 방향으로 생각하게했다. 방금 ​​놀았을 때 "id"를 검색하는 것이 빠른 작업이며 작은 수의 "ids"로 업데이트를 제한하는 것이 LIMIT를 사용하는 것보다 10 배 빠름을 발견했습니다. 어쩌면 가장 낮은 가능한 ID를 찾은 다음 동일한 UPDATE – John

0

"20,000"에서 "0"으로 성능 비용이 절감되었습니다.
mysql이 이전에 불가능했던 방식으로 인덱스에서 완전히 벗어날 수있는 것 같습니다. 작업이 sequenzial을 추가 된 것처럼

$id=select MIN(id) from task_jobs where customer_job_id=31 AND assigned_time is NULL) AND assigned_time is NULL; 

select id FROM task_jobs WHERE customer_job_id=31 AND client_reserved=0 AND 
result_delivered=0 AND (assigned_instance is NULL) AND 
id >= $id LIMIT 10; 

것은 그냥 거기에 검색을 시작하는 가장 낮은 ID (서브 쿼리) 및 제한 mysql을 찾을 수 있습니다.
갑자기 비용이 20k에서 0-10으로 감소합니다.

관련 문제