문제

나는 최근에 내가 작업하고있는 사이트에서 버그를 발견하고 수정하여 수백만 개의 중복 데이터를 테이블에 (여전히 수백만에 이르렀을 때) 상당히 큰 데이터를 얻었습니다. 이 중복 행을 쉽게 찾을 수 있으며 단일 삭제 쿼리를 실행하여 모두 죽일 수 있습니다. 문제는 한 번의 샷 으로이 많은 행을 삭제하려고 시도하면 오랫동안 테이블을 잠그고 가능한 경우 피하고 싶습니다. 사이트를 무너 뜨리지 않고 (테이블을 잠그면)이 행을 제거 할 수있는 유일한 방법은 다음과 같습니다.

  1. 루프에서 수천 개의 작은 삭제 쿼리를 실행하는 스크립트를 작성하십시오. 다른 쿼리가 큐에 들어가 삭제 사이에 실행할 수 있기 때문에 이론적으로 잠긴 테이블 문제를 해결할 수 있습니다. 그러나 여전히 데이터베이스의로드를 상당히 스파이크하고 실행하는 데 시간이 오래 걸릴 것입니다.
  2. 테이블의 이름을 바꾸고 기존 테이블을 재현하십시오 (이제 비어 있습니다). 그런 다음 이름이 변경된 테이블에서 정리하십시오. 새 테이블의 이름을 바꾸고 오래된 테이블의 이름을 지정하고 새 행을 이름 바꾸기 테이블로 병합하십시오. 이것은 훨씬 더 많은 단계를 거치지 만 최소한의 중단으로 작업을 완료해야합니다. 여기서 유일한 까다로운 부분은 문제의 테이블이보고 테이블이라는 것입니다. 일단 길에서 이름이 바뀌고 빈 공간이 그 자리에 놓여있는 모든 역사적 보고서가 내가 다시 제자리에 놓을 때까지 사라집니다. 또한 데이터 유형의 데이터 유형으로 인해 병합 프로세스가 약간의 고통이 될 수 있습니다. 전반적으로 이것은 지금 나의 선택입니다.

나는 다른 사람이 이전 에이 문제를 겪었는지 궁금해하고 있다면, 그렇다면 사이트를 무너 뜨리지 않고 어떻게 처리했는지, 그리고 사용자에 대한 중단이 최소화되기를 바랍니다. 2 번 또는 다른 비슷한 접근 방식을 사용하면 밤 늦게 달리기 위해 물건을 예약하고 다음날 아침 일찍 합병을 할 수 있으며 사용자에게 미리 알리면 큰 문제가되지 않습니다. 나는 누군가가 더 나은 또는 더 쉽게 정리하는 방법에 대한 아이디어가 있는지보고 싶어합니다.

도움이 되었습니까?

해결책

DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000

씻고 헹구고 0 행이 영향을받을 때까지 반복하십시오. 아마도 반복 사이에 1 ~ 3 번 동안 잠을자는 스크립트에서.

다른 팁

또한 테이블에 몇 가지 제약 조건을 추가하여 다시 발생하지 않도록 권장합니다. 샷 당 1000 개의 수백만 행은 스크립트를 1000 회 반복하여 완료합니다. 스크립트가 3.6 초마다 한 번씩 실행되면 한 시간 안에 완료됩니다. 걱정 마. 고객이 눈치 채지 못할 것입니다.

다음은 한 번에 하나씩 1,000,000 레코드를 삭제합니다.

 for i in `seq 1 1000`; do 
     mysql  -e "select id from table_name where (condition) order by id desc limit 1000 " | sed 's;/|;;g' | awk '{if(NR>1)print "delete from table_name where id = ",$1,";" }' | mysql; 
 done

당신은 그것들을 함께 그룹화하고 (id1, id2, .. idn) in table_name을 삭제할 수 있습니다.

MySQL의 25m+ 행 테이블에서 1m+ 행을 삭제하는 사용 사례가있었습니다. 배치 삭제와 같은 다른 접근법을 시도했습니다 (위에서 설명).
가장 빠른 방법 (새로운 테이블에 필요한 레코드의 사본)을 알았습니다.

  1. ID 만 보유하는 임시 테이블을 만듭니다.

테이블을 작성 id_temp_table (temp_id int);

  1. 제거해야 할 ID를 삽입하십시오.

id_temp_table (temp_id)에 삽입 .....

  1. 새 table_new를 만들었습니다

  2. id_temp_table에있는 불필요한 행없이 테이블에서 table_new로 모든 레코드를 삽입하십시오.

table_new에 삽입 .... 여기서 table_id가 아닌 곳 (id_temp_table에서 별개의 (temp_id));

  1. 테이블의 이름을 바꿉니다

전체 과정은 ~ 1 시간이 걸렸습니다. 내 사용 사례에서 100 레코드에서 배치의 단순한 삭제는 10 분이 걸렸습니다.

나는 사용할 것이다 MK-Archiver 우수한 Maatkit 유틸리티 패키지 (MySQL 관리를위한 Perl Scripts) Maatkit은 O'Reilly "High Performance MySQL"책의 저자 인 Baron Schwartz의 출신입니다.

목표는 OLTP 쿼리에 큰 영향을 미치지 않고 기존 데이터를 테이블 밖으로 삭제하는 데 영향을 미치고 앞으로 전용되는 작업입니다. 동일한 서버에있을 필요가없는 다른 테이블에 데이터를 삽입 할 수 있습니다. 로드 데이터에 적합한 형식으로 파일에 쓸 수도 있습니다. 또는 아무도 할 수 없으며,이 경우 점진적인 삭제 일뿐입니다.

원치 않는 행을 작은 배치로 보관하기 위해 이미 제작되었으며 보너스로 제거 할 행을 선택하는 쿼리를 망칠 경우 삭제 된 행을 파일에 저장할 수 있습니다.

설치가 필요하지 않고 그냥 잡으십시오 http://www.maatkit.org/get/mk-archiver 문서를 위해 Perldoc을 실행하거나 웹 사이트를 읽습니다.

나는 비슷한 문제에 직면했다. 우리는 분할이없고 1 차 _key 열에 단 하나의 인덱스 만있는 크기가 약 500GB 인 정말 큰 테이블을 가졌습니다. 우리의 주인은 기계, 128 코어 및 512 기가의 RAM의 헐크였으며 우리도 여러 노예가있었습니다. 우리는 대규모 행 삭제를 해결하기 위해 몇 가지 기술을 시도했습니다. 나는 우리 모두가 찾은 최악의 것에서 여기에 모두 나열 할 것입니다.

  1. 한 번에 한 줄을 가져오고 삭제합니다. 이것은 당신이 할 수있는 절대 최악입니다. 그래서 우리는 이것을 시도조차하지 않았습니다.
  2. Primary_key 열의 한계 쿼리를 사용하여 데이터베이스에서 첫 번째 'X'행을 가져온 다음 행 IDS를 확인하여 응용 프로그램에서 삭제하고 기본 _key ID 목록으로 단일 삭제 쿼리를 발사합니다. 따라서 'x'행당 2 쿼리. 이제이 접근법은 괜찮 았지만 배치 작업을 사용 하여이 작업을 수행하는 것은 10 분 정도 약 5 백만 행을 삭제했으며, 이로 인해 MySQL DB의 노예가 105 초 정도 지연되었습니다. 10 분 활성에서 105 초 지연. 그래서 우리는 멈춰야했습니다.
  3. 이 기술에서, 우리는 후속 배치 페치와 크기 'X'의 삭제 사이에 50ms 지연을 도입했습니다. 이것은 지연 문제를 해결했지만 이제는 기술 #2의 5 백만에 비해 10 분당 1.2-130 만 행을 삭제했습니다.
  4. 데이터베이스 테이블을 분할 한 다음 필요하지 않은 경우 전체 파티션을 삭제합니다. 이것은 우리가 가지고있는 최고의 솔루션이지만 사전 정당화 된 테이블이 필요합니다. 우리는 Primary_key 열에서 인덱싱만으로 정당하지 않은 매우 오래된 테이블을 가지고 있었기 때문에 3 단계를 따랐습니다. 파티션을 만드는 데 너무 많은 시간이 걸렸고 우리는 위기 모드에있었습니다. 다음은 도움이 된 파티션과 관련된 몇 가지 링크입니다. 공식 MySQL 참조, Oracle DB 매일 파티셔닝.

따라서 IMO, 테이블에서 파티션을 만들 수있는 사치를 가질 수 있다면 옵션 #4를 찾으십시오. 그렇지 않으면 옵션 #3에 갇혀 있습니다.

한 번에 2000 행을 말하면 배치로 수행하십시오. 중간에 커밋하십시오. 백만 행은 그다지 많지 않으며 테이블에 많은 색인이 없으면 빠질 것입니다.

에 따르면 MySQL 문서, TRUNCATE TABLE 빠른 대안입니다 DELETE FROM. 이 시도:

TRUNCATE TABLE table_name

나는 이것을 50m 줄로 시도했고 2 분 안에 이루어졌다.

참고 : Truncate 운영은 거래 안전이 아닙니다. 활성 트랜잭션 또는 활성 테이블 잠금 과정에서 오류가 발생합니다.

우리를 위해 DELETE WHERE %s ORDER BY %s LIMIT %d 대답은 옵션이 아니 었습니다. 여기서 기준이 느려지지 않았고 (무인재가 아닌 열) 마스터를 때리기 때문에 답변은 옵션이 아닙니다.

삭제하려는 기본 키 목록을 읽으십시오. 이런 종류의 형식으로 내보내기 :

00669163-4514-4B50-B6E9-50BA232CA5EB
00679DE5-7659-4CD4-A919-6426A2831F35

다음 배쉬 스크립트를 사용 하여이 입력을 잡고 삭제 문으로 청크하십시오. 때문에 Bash ≥ 4가 필요합니다 mapfile 내장]:

sql-chunker.sh (기억해 chmod +x 나, 그리고 Shebang을 변경하여 Bash 4 실행 파일을 가리 킵니다):

#!/usr/local/Cellar/bash/4.4.12/bin/bash

# Expected input format:
: <<!
00669163-4514-4B50-B6E9-50BA232CA5EB
00669DE5-7659-4CD4-A919-6426A2831F35
!

if [ -z "$1" ]
  then
    echo "No chunk size supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

if [ -z "$2" ]
  then
    echo "No file supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

function join_by {
    local d=$1
    shift
    echo -n "$1"
    shift
    printf "%s" "${@/#/$d}"
}

while mapfile -t -n "$1" ary && ((${#ary[@]})); do
    printf "DELETE FROM my_cool_table WHERE id IN ('%s');\n" `join_by "','" "${ary[@]}"`
done < "$2"

그렇게 호출 :

./sql-chunker.sh 1000 ids.txt > batch_1000.sql

이렇게하면 SO와 같은 출력 형식의 파일이 제공됩니다 (배치 크기 2를 사용했습니다).

DELETE FROM my_cool_table WHERE id IN ('006CC671-655A-432E-9164-D3C64191EDCE','006CD163-794A-4C3E-8206-D05D1A5EE01E');
DELETE FROM my_cool_table WHERE id IN ('006CD837-F1AD-4CCA-82A4-74356580CEBC','006CDA35-F132-4F2C-8054-0F1D6709388A');

그런 다음 SO와 같은 진술을 실행하십시오.

mysql --login-path=master billing < batch_1000.sql

익숙하지 않은 사람들을 위해 login-path, 명령 줄에서 암호를 입력하지 않고 로그인하는 바로 가기입니다.

속도는 MySQL의 "클러스터 된 인덱스"때문이라고 생각합니다. 여기서 실제 레코드는 기본 키 인덱스 내에 기본 키 인덱스의 순서로 저장됩니다. 즉, 기본 키를 통한 레코드에 대한 액세스는 매우 빠른 것을 의미합니다. 디스크의 레코드가 인덱스에서 올바른 기본 키를 찾은 디스크의 레코드가 하나만 필요한 디스크 페치 만 필요하기 때문입니다.

클러스터 된 인덱스가없는 다른 데이터베이스에서 인덱스 자체는 레코드를 보유하지 않고 레코드가 테이블 파일의 위치에 위치한 위치를 나타내는 "오프셋"또는 "위치"만 있으면 실제 데이터를 검색하기 위해 해당 파일에서 두 번째 페치를 작성해야합니다. .

클러스터 된 인덱스에서 레코드를 삭제할 때 테이블 위의 레코드 위의 모든 레코드가 인덱스에서 거대한 구멍을 피하기 위해 아래쪽으로 이동해야한다는 것을 상상할 수 있습니다 (적어도 몇 년 전부터 기억하는 것입니다. 이것을 변경했을 수 있습니다).

MySQL에서 실제로 삭제 된 것을 발견 한 것을 아는 것은 삭제를 역 순서로 수행하는 것이 었습니다. 이렇게하면 최소한의 레코드 움직임이 발생합니다. 첫 번째에서 레코드가 삭제되기 때문에 후속 삭제는 재배치 할 객체가 적다는 의미입니다.

나는 이것을하기 위해 아무것도 스크립팅하지 않았으며, 제대로 수행하려면 스크립트가 절대적으로 필요할 것이지만 또 다른 옵션은 새롭고 복제 된 테이블을 만들고 보관하려는 모든 행을 선택하는 것입니다. 트리거를 사용 하여이 프로세스가 완료되는 동안 최신 상태를 유지하십시오. 동기화 된 경우 (삭제하려는 행을 빼고) 트랜잭션에서 두 테이블의 이름을 바꾸어 새 제품이 기존을 대신 할 수 있도록하십시오. 오래된 테이블을 떨어 뜨리고 Voila!

이것은 (명백히) 많은 추가 디스크 공간이 필요하며 I/O 리소스에 세금을 부과 할 수 있지만 그렇지 않으면 훨씬 더 빠를 수 있습니다.

데이터의 특성이나 비상 사태에 따라 이전 테이블의 이름을 바꾸고 그 장소에 새롭고 빈 테이블을 만들고 여가에서 새 테이블에 "유지"행을 선택할 수 있습니다 ...

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top