MySQL 단일 테이블에서 천만 개 이상의 행을 가능한 한 빨리 업데이트하는 방법은 무엇입니까?


32

대부분의 테이블에 InnoDB 스토리지 엔진과 함께 MySQL 5.6 사용. InnoDB 버퍼 풀 크기는 15GB이고 Innodb DB + 인덱스는 약 10GB입니다. 서버에 32GB RAM이 있으며 Cent OS 7 x64를 실행 중입니다.

나는 약 천만 + 레코드를 포함하는 하나의 큰 테이블을 가지고 있습니다.

24 시간마다 원격 서버에서 업데이트 된 덤프 파일을 얻습니다. 파일은 csv 형식입니다. 해당 형식을 제어 할 수 없습니다. 파일 크기는 ~ 750MB입니다. MyISAM 테이블에 행마다 데이터를 삽입하려고 시도했지만 35 분이 걸렸습니다.

파일에서 10-12 행 중 한 줄에 3 개의 값만 가져 와서 데이터베이스에서 업데이트해야합니다.

이와 같은 것을 달성하는 가장 좋은 방법은 무엇입니까?

나는 이것을 매일해야한다.

현재 흐름은 다음과 같습니다.

  1. mysqli_begin_transaction
  2. 라인별로 덤프 파일 읽기
  3. 각 레코드를 한 줄씩 업데이트하십시오.
  4. mysqli_commit

위의 작업 을 완료하는 데 약 30-40 분이 걸리며이 작업 을 수행하는 동안 다른 업데이트가 진행 중입니다.

잠금 대기 시간 초과를 초과했습니다. 거래를 다시 시작하십시오

업데이트 1

사용하여 새 테이블에 데이터로드 LOAD DATA LOCAL INFILE. MyISAM에서는 38.93 secInnoDB에서 7 분 5.21 초가 걸렸습니다. 그런 다음 :

UPDATE table1 t1, table2 t2
SET 
t1.field1 = t2.field1,
t1.field2 = t2.field2,
t1.field3 = t2.field3
WHERE t1.field10 = t2.field10

Query OK, 434914 rows affected (22 hours 14 min 47.55 sec)

업데이트 2

조인 쿼리와 동일한 업데이트

UPDATE table1 a JOIN table2 b 
ON a.field1 = b.field1 
SET 
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4

(14 hours 56 min 46.85 sec)

의견의 질문에 대한 설명 :

  • 테이블에있는 행의 약 6 %가 파일에 의해 업데이트되지만 때로는 25 %까지 될 수 있습니다.
  • 업데이트되는 필드에 색인이 있습니다. 테이블에는 12 개의 인덱스가 있으며 8 개의 인덱스에는 업데이트 필드가 포함됩니다.
  • 한 트랜잭션에서 업데이트를 수행 할 필요 는 없습니다 . 시간이 걸리지 만 24 시간을 넘지 않아야합니다. 나중에이 테이블에 의존하는 스핑크스 인덱스를 업데이트해야하기 때문에 전체 테이블을 잠그지 않고 1 시간 안에 완료하려고합니다. 데이터베이스가 다른 작업에 사용 가능한 경우 단계가 더 오래 걸리는 것은 중요하지 않습니다.
  • 전처리 단계에서 CSV 형식을 수정할 수 있습니다. 중요한 것은 빠른 업데이트와 잠금이없는 것입니다.
  • 표 2는 MyISAM이다. 데이터로드 파일을 사용하여 csv 파일에서 새로 작성된 테이블입니다. MYI 파일 크기는 452 MB입니다. 표 2는 field1 열에서 색인화됩니다.
  • MyISAM 테이블의 MYD는 663MB입니다.

업데이트 3 :

다음은 두 테이블에 대한 자세한 내용입니다.

CREATE TABLE `content` (
  `hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `og_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `files_count` smallint(5) unsigned NOT NULL DEFAULT '0',
  `more_files` smallint(5) unsigned NOT NULL DEFAULT '0',
  `files` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
  `category` smallint(3) unsigned NOT NULL DEFAULT '600',
  `size` bigint(19) unsigned NOT NULL DEFAULT '0',
  `downloaders` int(11) NOT NULL DEFAULT '0',
  `completed` int(11) NOT NULL DEFAULT '0',
  `uploaders` int(11) NOT NULL DEFAULT '0',
  `creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `upload_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `last_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `vote_up` int(11) unsigned NOT NULL DEFAULT '0',
  `vote_down` int(11) unsigned NOT NULL DEFAULT '0',
  `comments_count` int(11) NOT NULL DEFAULT '0',
  `imdb` int(8) unsigned NOT NULL DEFAULT '0',
  `video_sample` tinyint(1) NOT NULL DEFAULT '0',
  `video_quality` tinyint(2) NOT NULL DEFAULT '0',
  `audio_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
  `subtitle_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
  `verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `uploader` int(11) unsigned NOT NULL DEFAULT '0',
  `anonymous` tinyint(1) NOT NULL DEFAULT '0',
  `enabled` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `tfile_size` int(11) unsigned NOT NULL DEFAULT '0',
  `scrape_source` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `record_num` int(11) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`record_num`),
  UNIQUE KEY `hash` (`hash`),
  KEY `uploaders` (`uploaders`),
  KEY `tfile_size` (`tfile_size`),
  KEY `enabled_category_upload_date_verified_` (`enabled`,`category`,`upload_date`,`verified`),
  KEY `enabled_upload_date_verified_` (`enabled`,`upload_date`,`verified`),
  KEY `enabled_category_verified_` (`enabled`,`category`,`verified`),
  KEY `enabled_verified_` (`enabled`,`verified`),
  KEY `enabled_uploader_` (`enabled`,`uploader`),
  KEY `anonymous_uploader_` (`anonymous`,`uploader`),
  KEY `enabled_uploaders_upload_date_` (`enabled`,`uploaders`,`upload_date`),
  KEY `enabled_verified_category` (`enabled`,`verified`,`category`),
  KEY `verified_enabled_category` (`verified`,`enabled`,`category`)
) ENGINE=InnoDB AUTO_INCREMENT=7551163 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=FIXED


CREATE TABLE `content_csv_dump_temp` (
  `hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `category_id` int(11) unsigned NOT NULL DEFAULT '0',
  `uploaders` int(11) unsigned NOT NULL DEFAULT '0',
  `downloaders` int(11) unsigned NOT NULL DEFAULT '0',
  `verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

여기에 content데이터를 사용하여 테이블 을 업데이트하는 업데이트 쿼리가 있습니다.content_csv_dump_temp

UPDATE content a JOIN content_csv_dump_temp b 
ON a.hash = b.hash 
SET 
a.uploaders = b.uploaders,
a.downloaders = b.downloaders,
a.verified = b.verified

업데이트 4 :

위의 모든 테스트는 테스트 시스템에서 수행되었지만 이제 프로덕션 시스템에서 동일한 테스트를 수행했으며 쿼리 속도가 매우 빠릅니다.

mysql> UPDATE content_test a JOIN content_csv_dump_temp b
    -> ON a.hash = b.hash
    -> SET
    -> a.uploaders = b.uploaders,
    -> a.downloaders = b.downloaders,
    -> a.verified = b.verified;
Query OK, 2673528 rows affected (7 min 50.42 sec)
Rows matched: 7044818  Changed: 2673528  Warnings: 0

내 실수에 대해 사과드립니다. 각 레코드 업데이트 대신 결합을 사용하는 것이 좋습니다. 이제 rick_james가 제안한 색인을 사용하여 mpre를 개선하려고합니다. 벤치 마킹이 완료되면 업데이트됩니다.


당신은 어떤 순서 로 합성물 을 가지고 INDEX(field2, field3, field4)있습니까? 우리에게 보여주세요 SHOW CREATE TABLE.
Rick James

1
12 및 8 인덱스는 문제의 심각한 부분입니다. MyISAM은 또 다른 심각한 부분입니다. InnoDB 또는 TokuDB는 여러 인덱스에서 훨씬 더 잘 수행됩니다.
Rick James

가지가 UPDATEs 있습니다. csv 데이터에서 테이블을 업데이트 할 때의 간단한 설명이 무엇인지 정확하게 알려주십시오 . 그런 다음 요구 사항을 충족하는 기술을 고안하는 데 도움을 줄 수 있습니다.
Rick James

@RickJames 하나만 있으며 update업데이트 된 질문을 확인하십시오., 감사
AMB

답변:


17

내 경험을 바탕으로 LOAD DATA INFILE 을 사용 하여 CSV 파일을 가져옵니다.

LOAD DATA INFILE 문은 텍스트 파일의 행을 매우 빠른 속도로 테이블로 읽습니다.

인터넷 로드 데이터 예제 에서 찾은 예제 입니다. 상자 에서이 예제를 테스트하고 정상적으로 작동했습니다.

예제 테이블

CREATE TABLE example (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Column2` varchar(14) NOT NULL,
  `Column3` varchar(14) NOT NULL,
  `Column4` varchar(14) NOT NULL,
  `Column5` DATE NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB

CSV 파일 예

# more /tmp/example.csv
Column1,Column2,Column3,Column4,Column5
1,A,Foo,sdsdsd,4/13/2013
2,B,Bar,sdsa,4/12/2013
3,C,Foo,wewqe,3/12/2013
4,D,Bar,asdsad,2/1/2013
5,E,FOObar,wewqe,5/1/2013

MySQL 콘솔에서 실행할 Import 문

LOAD DATA LOCAL INFILE '/tmp/example.csv'
    -> INTO TABLE example
    -> FIELDS TERMINATED BY ','
    -> LINES TERMINATED BY '\n'
    -> IGNORE 1 LINES
    -> (id, Column3,Column4, @Column5)
    -> set
    -> Column5 = str_to_date(@Column5, '%m/%d/%Y');

결과

MySQL [testcsv]> select * from example;
+----+---------+---------+---------+------------+
| Id | Column2 | Column3 | Column4 | Column5    |
+----+---------+---------+---------+------------+
|  1 |         | Column2 | Column3 | 0000-00-00 |
|  2 |         | B       | Bar     | 0000-00-00 |
|  3 |         | C       | Foo     | 0000-00-00 |
|  4 |         | D       | Bar     | 0000-00-00 |
|  5 |         | E       | FOObar  | 0000-00-00 |
+----+---------+---------+---------+------------+

IGNORE는 단순히 열 머리글 인 첫 번째 행을 무시합니다.

IGNORE 이후, 가져올 열 (건너 뛰기 column2)을 지정합니다. 이는 질문의 기준 중 하나와 일치합니다.

다음은 Oracle에서 직접 가져온 또 다른 예입니다. LOAD DATA INFILE 예

시작하기에 충분해야합니다.


임시 테이블에 데이터를로드하는 데로드 데이터를 사용한 다음 다른 쿼리를 사용하여 기본 테이블에서 데이터를 업데이트 할 수 있습니다. 감사합니다
AMB

14

언급 된 모든 사항에 비추어 병목 현상은 결합 자체처럼 보입니다.

ASPECT # 1 : 결합 버퍼 크기

십중팔구, 당신의 join_buffer_size는 아마 너무 낮습니다.

MySQL이 Join Buffer Cache를 사용하는 방법 에 대한 MySQL 문서에 따르면

사용 된 열은 전체 행이 아닌 조인 버퍼에만 저장합니다.

이 경우 조인 버퍼의 키를 RAM에 유지하십시오.

각 키마다 1000 만 행에 4 바이트를 곱합니다. 약 40M입니다.

세션에서 42M (40M보다 약간 큼)으로 부딪 히십시오.

SET join_buffer_size = 1024 * 1024 * 42;
UPDATE table1 a JOIN table2 b 
ON a.field1 = b.field1 
SET 
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4;

이것이 트릭을 수행하면 추가하십시오. my.cnf

[mysqld]
join_buffer_size = 42M

새 연결에는 mysqld를 다시 시작할 필요가 없습니다. 그냥 뛰어

mysql> SET GLOBAL join_buffer_size = 1024 * 1024 * 42;

ASPECT # 2 : 조인 작업

옵티 마이저를 tweeking하여 조인 작업 스타일을 조작 할 수 있습니다.

블록 중첩 루프 및 배치 키 액세스 조인 에 대한 MySQL 설명서에 따르면

BKA를 사용하는 경우 join_buffer_size 값은 스토리지 엔진에 대한 각 요청의 키 배치 크기를 정의합니다. 버퍼가 클수록 조인 작업의 오른쪽 테이블에 대한 순차적 액세스가 많아 져 성능이 크게 향상 될 수 있습니다.

BKA를 사용하려면 optimizer_switch 시스템 변수의 batched_key_access 플래그를 on으로 설정해야합니다. BKA는 MRR을 사용하므로 mrr 플래그도 켜져 있어야합니다. 현재 MRR의 비용 산정은 너무 비관적입니다. 따라서 BKA를 사용하려면 mrr_cost_based를 해제해야합니다.

동일한 페이지에서 다음을 수행하는 것이 좋습니다.

mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

ASPECT # 3 : 디스크에 업데이트 쓰기 (선택 사항)

대부분은 innodb_write_io_threads 를 증가시켜 더티 페이지를 버퍼 풀에서 더 빨리 기록 하는 것을 잊지 마십시오 .

[mysqld]
innodb_write_io_threads = 16

이 변경을 위해 MySQL을 다시 시작해야합니다

시도 해봐 !!!


좋은! 조정 가능한 결합 버퍼 팁의 경우 +1 참여해야한다면 메모리에 참여하십시오. 좋은 팁!
Peter Dixon-Moses

3
  1. CREATE TABLE CSV와 일치
  2. LOAD DATA 그 테이블에
  3. UPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
  4. DROP TABLE csv_table;

3 단계는 행 단위보다 훨씬 빠르지 만 사소한 시간 동안 테이블의 모든 행을 계속 잠급니다. 이 잠금 시간이 전체 프로세스에 걸리는 시간보다 더 중요한 경우 ...

테이블에 아무것도 쓰지 않으면 ...

  1. CREATE TABLECSV와 일치합니다. 어떤 인덱스는에 필요한 것을 제외시켰다 JOIN에서 UPDATE. 고유 한 경우 확인하십시오 PRIMARY KEY.
  2. LOAD DATA 그 테이블에
  3. 복사 real_tablenew_table( CREATE ... SELECT)
  4. UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
  5. RENAME TABLE real_table TO old, new_table TO real_table;
  6. DROP TABLE csv_table, old;

특히 불필요한 인덱스가 제거 된 경우 3 단계가 업데이트보다 빠릅니다.
5 단계는 "즉시"입니다.


예를 들어 3 단계 후에 4 단계를 수행하면 새 데이터가 real_table에 삽입되어 new_table에서 해당 데이터를 놓치게됩니다. 이에 대한 해결 방법은 무엇입니까? 감사합니다
AMB

무엇을 참조하십시오 pt-online-schema-digest; 를 통해 이러한 문제를 처리합니다 TRIGGER.
Rick James

의 테이블에 인덱스가 필요 하지 않을 수 있습니다 LOAD DATA. 불필요한 인덱스를 추가하는 데 많은 시간이 소요됩니다.
Rick James

최신 정보를 기반으로, 나는 CSV 파일이 MyISAM 테이블에로드되고 AUTO_INCREMENTPK를 기반으로 한 번에 1K 행을 청킹 하는 데 기울고 있습니다. 그러나 세부 사항을 설명하기 전에 모든 요구 사항 테이블 스키마 를 확인해야합니다 .
Rick James

해시를로 설정 PRIMARY index했지만 주문 쿼리를 사용하여 50k로 청킹하는 데 더 많은 시간이 걸립니다. 자동 증가를 만들면 더 좋을까요? 그리고 그것을 PRIMARY index? 로 설정하십시오
AMB

3

당신은 말했다 :

  • 업데이트는 테이블의 6-25 %에 영향을줍니다.
  • 가능한 빨리 (1 시간 미만)
  • 잠그지 않고
  • 단일 거래일 필요는 없습니다
  • 그러나 (Rick James 답변에 대한 의견에서) 경쟁 조건에 대한 우려를 표명합니다.

이러한 진술 중 다수는 모순 될 수 있습니다. 예를 들어, 테이블을 잠그지 않는 대규모 업데이트. 또는 하나의 거대한 트랜잭션을 사용하지 않고 경쟁 조건을 피하십시오.

또한 테이블이 많이 색인화되므로 삽입 및 업데이트가 느려질 수 있습니다.


경쟁 조건 피하기

업데이트 된 타임 스탬프를 테이블 에 추가 할 수 있으면 단일 트랜잭션에서 50 만 건의 업데이트를 기록하지 않으면 서 경쟁 조건을 해결할 수 있습니다.

이렇게하면 (현재와 같이) 행 단위 업데이트를 수행 할 수 있지만 자동 커밋 또는보다 합리적인 트랜잭션 일괄 처리가 가능합니다.

이후 업데이트가 아직 발생하지 않았는지 확인하여 경쟁 조건 (라인별로 업데이트하는 동안)을 피합니다 ( UPDATE ... WHERE pk = [pk] AND updated < [batchfile date])

그리고 중요 하게는 병렬 업데이트 를 실행할 수 있습니다 .


최대한 빨리 실행 — 병렬화

이 타임 스탬프 확인이 완료되었습니다.

  1. 배치 파일을 적당한 크기의 청크로 분할하십시오 (예 : 50,000 행 / 파일)
  2. 동시에, 각 파일에서 스크립트를 읽고 50,000 개의 UPDATE 문이있는 파일을 출력하십시오.
  3. 동시에 (2) 완료되면 mysql각 SQL 파일 을 실행하십시오.

(예에서 bashsplit하고 xargs -P쉽게 할 수있는 방법을 명령 여러 가지 평행을 실행합니다. 병렬 처리의 유무에 따라 달라집니다 얼마나 많은 스레드 당신이하고있는에 헌신하고자 갱신 )


"line-by-line"은 최소 100 배치로 작업하는 것보다 10 배 느리다는 것을 명심하십시오.
Rick James

이 경우 확실하게 벤치마킹해야합니다. 테이블의 6-25 %를 업데이트하면 (업데이트 된 열에 8 개의 인덱스가 포함됨) 인덱스 유지 관리에 병목 현상이 발생할 가능성이 있습니다.
피터 딕슨-모세

내 말은, 경우에 따라 인덱스를 삭제하고, 대량 업데이트하고, 다시 생성하는 것이 더 빠를 수도 있지만 OP는 다운 타임을 원하지 않습니다.
피터 딕슨-모세

1

대규모 업데이트는 I / O 바운드입니다. 내가 제안 할게:

  1. 자주 업데이트되는 3 개의 필드를 저장할 고유 한 테이블을 만듭니다. 정적 데이터를 유지하는 곳은 asset_static 한 테이블 , 업 로더, 다운로더 및 검증을 저장 하는 다른 asset_dynamic 테이블을 호출 해 봅시다 .
  2. 가능하면 assets_dynamic 테이블에 MEMORY 엔진을 사용하십시오 . (각 업데이트 후 디스크 백업).
  3. 업데이트 4에 따라 가볍고 민첩한 asset_dynamic 을 업데이트하십시오 (예 : LOAD INFILE ... INTO temp; UPDATE assets_dynamic a JOIN temp b on a.id = b.id SET [업데이트해야 할 내용]. (시스템에서 asset_dynamic 은 95M 개의 행을 가지며 업데이트는 40 초가 조금 넘는 6 백만 행에 영향을 미칩니다.)
  4. Sphinx의 인덱서를 실행할 때 asset_staticassets_dynamic을 결합하십시오 (이러한 필드 중 하나를 속성으로 사용하려는 경우).

0

UPDATE빨리 실행하려면

INDEX(uploaders, downloaders, verified)

어느 테이블 에나있을 수 있습니다. 세 개의 필드는 임의의 순서로있을 수 있습니다.

그러면 UPDATE두 테이블 사이의 행을 빠르게 일치시킬 수 있습니다.

그리고 두 테이블에서 데이터 유형을 동일하게 만듭니다 (둘 다 INT SIGNED또는 둘 다 INT UNSIGNED).


실제로 업데이트 속도가 느려졌습니다.
AMB

흠 ...을 제공하십시오 EXPLAIN UPDATE ...;.
Rick James
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.