2016-11-03 4 views
1

큰 테이블에서 데이터를 가져 오기 위해 Python MySQLdb를 사용하여 매우 긴 실행 시간 (긴 페치 시간과 반대되는)이 있으며, 분명히 잘못된 것이 있는지 이해하고 싶습니다.Python MySQLdb가 느리게 실행됩니다.

create table mytable(
    a varchar(3), 
    b bigint, 
    c int, 
    d int, 
    e datetime, 
    f varchar(20), 
    g varchar(10), 
    primary key(a, b, c, d)) 
ENGINE=InnoDB; 

그것은 현재 1 억 5000 만 행을 포함하고 테이블 크기 추정 19기가바이트입니다 :

내 테이블은 다음과 같이 정의된다. execute 명령에 소요되는 시간에서 오는

import MySQLdb 
database = MySQLdb.connect(passwd="x", host="dbserver", user="user", db="database", port=9999) 
mysql_query = """select a, b, c, d, e, f, g from mytable use index (primary) where a = %s order by a, b, c, d""" 
mysql_cursor = database.cursor() 
mysql_cursor.execute(mysql_query, ["AA"]) 
for a, b, c, d, e, f, g in mysql_cursor: 
    #Do something 

내 놀라움을 다음과 같이

파이썬 코드입니다. 비록 내가 execute이 (거의 기본 키를 사용하여 테이블을 통과해야하므로) 거의 시간을 소비하지 않고, for 루프에서 보낸 꽤 오랜 시간을 보냈지 만 litteraly는 여기에 오래 머무른다.

설명 할 계획은 다음과 같습니다

순간
explain select a, b, c, d, e, f, g from mytable use index (primary) where a = %s order by a, b, c, d 
'1','SIMPLE','eventindex','ref','PRIMARY','PRIMARY','5','const','87402369','Using where' 

는, 모든 행이 (나중에 다른 값을 추가 할 생각했던 열에서 같은 값을 포함하지만, 순간 열 분포에서 콘텐츠가 실제로 균형을 이루지는 않음). 열 b가 더 잘 분산 됨

MySQL이 쿼리를 실행하는 데 너무 많은 시간을 소비하고 있다고 설명 할 수 있습니다 (행을 가져 오는 데 시간을 소비하는 것과 반대).

보너스 이 유스 케이스를 최적화하기위한 명백한 빠른 승리? B 열에 테이블 분할? 열? 열을 제거하고 별도의 테이블을 대신 사용 하시겠습니까?

답변

0

실제로 MySQL의 질문과 비슷합니다. 실제로 문제는 파이썬이나 mysql-python과 관련이 없습니다.

WRT/SQL 물건 : - 그리고 실제로 더 많은 디스크 액세스 충분히 선택이 아니다 인덱스는 당신을 아주 인덱스 트리 탐색뿐만 아니라에 순차적 스캔 을하고 결국 때문에 유해 할 수 있습니다 (너무 많은 유사한 값을가집니다) 일반 테이블 스캔을 사용하면 양쪽에서 느슨해집니다 (IOW : 인덱스 트리 탐색의 오버 헤드 만 얻지 만 이점은 없습니다). 귀하의 경우에는 MySQL: low cardinality/selectivity columns = how to index? 여기 Role of selectivity in index scan/seek

당신이 use index 절없이 쿼리를 시도 할 수 있습니다, 그리고 아마 대신 ignore index clause를 사용하여 일반 바이 패스 인덱스를 최적화를 강제로 : 당신이 여기에서 자세한 내용을 확인할 수 있습니다.

0

다음은 MySQL에서의 일반적인 동작입니다. 다양한 소스에서 MySQL의 실행 단계에서 대부분의 선택 작업이 수행되는 것처럼 보입니다. 가져 오는 동안 네트워크 전송 만 수행됩니다. 필자는 오라클과 함께 많은 시간을 보냈습니다 (실제로 실행은 거의 아무 것도 수행하지 않으며 처리의 고기는 가져 오기 시간에 발생합니다). MySQL이 다르게 작동 할 수 있다는 것을 인식하지 못했습니다.

컨텍스트에 따라 항목을 통한 대기 시간없이 반복 할 수있는 해결 방법은 페이징 시스템을 구현하는 것일 수 있습니다. 파이썬 생성기에서 작은 페치를 캡슐화하여 수행 할 수 있습니다. 반면에, 우리는 호출간에 데이터의 일관성을 잃어 버리고 있지만 이것은 제 경우에는 받아 들일 수 있습니다. 이 접근법에 관심이있는 사람들을위한 기초가 있습니다.다음 페이지를 가져 오는 데 필요한 적응은 SQL 쿼리를 어떻게 든 complex 복잡하고 덜 유지할 수있게 만들고, 원하는 것보다 기본 키 구조에 코드를 바인딩 할 수 있습니다. 따라서 장단점을 먼저 고려해야합니다. 이것을 위해. 한 가지 좋은 소식은 이러한 복잡성이 발전기 뒤에 숨어있을 수 있다는 것입니다. MySQL을

import MySQLdb 
database = MySQLdb.connect(passwd="x", host="dbserver", user="user", db="database", port=9999) 

def get_next_item(database): #Definition of this generator encapsulating the paging system 
    first_call = True 
    mysql_cursor = database.cursor() 
    nothing_more_found = False 
    while not nothing_more_found: 
     mysql_query = """select a, b, c, d, e, f, g from mytable use index (primary) 
      where a = %s order by a, b, c, d 
      limit 10000""" if first_call else """select a, b, c, d, e, f, g from mytable use index (primary) 
      where a = %s and ((b > %s) or (b = %s and c > %s) or (b = %s and c = %s and d > %s)) 
      order by a, b, c, d 
      limit 10000""" 

     if first_call: 
      mysql_cursor.execute(mysql_query, ["AA", last_b, last_b, last_c, last_b, last_c, last_d]) 
      first_call = False 
     else: 
      mysql_cursor.execute(mysql_query, ["AA"]) 
     if mysql_cursor.rowcount == 0: 
      nothing_more_found = True 
     for a, b, c, d, e, f, g in mysql_cursor: 
      yield (a, b, c, d, e, f, g) 
      last_b, last_c, last_d = b, c, d 

for a, b, c, d, e, f, g in get_next_item(database): #Usage of the generator 
    #Do something 

설명 실행 대 마이크 Lischke가에서이 post에서 가져옵니다.

가져 오기 시간은 순수하게 결과를 전송하는 데 걸리는 시간 인 이며 이는 쿼리 실행과 전혀 관련이 없습니다. 쿼리 가져 오기는 쿼리를 실행할 때마다 다를 수 있습니다. 귀하의 네트워크 연결이 귀하의 질의가 얼마나 좋은지 또는 나쁜지를 결정해야하는 이유는 무엇입니까? 좋아요, 한 사용 실제로 존재합니다 : 쿼리가 너무 많은 데이터를 반환하는 경우 전송은 훨씬 오래 걸립니다. 그러나 때때로 이것은 결과가 캐싱되기 때문에 전체적으로 사실이 아니기 때문에 더 빨리 보낼 수 있습니다.

반면 Oracle에서는 select 중에 대부분의 작업이 가져 오기 중에 수행됩니다. 꽤 잘 정의 된, 즉의 prepareStatement입니다 - - 우리가 소프트 또는 하드 구문 분석을 수행 파악, 문을 컴파일이 구문 분석 톰 카이트 자신 그것의 here

생각이 방법

1)에 의해 설명 실행하는 방법.

2) 실행 - 명령문을 엽니 다. 업데이트의 경우, 삭제의 경우 삽입 - 해당 문을 OPEN 할 때 실행합니다. 모든 작업이 여기에서 발생합니다.

선택하면 더 복잡합니다. 대부분의 셀렉트는 실행 중에 ZERO 작업을 수행합니다. 커서가 열리고 있습니다 - 커서는 계획이있는 공유 풀의 공간에 포인터입니다. 바인드 변수 값, 쿼리의 "as of"시간을 나타내는 SCN - 짧게 이 시점에서 커서가 귀하의 컨텍스트, 귀하의 가상 머신 상태는 SQL 계획을 마치 바이트 코드로 생각합니다 (그것은) 가상 머신에서 프로그램으로 실행됩니다 (그렇습니다). 커서는 명령 포인터 (명령문의 실행 ), 상태 (레지스터와 같은 것) 등입니다. 일반적으로 select는 여기에 아무것도 수행하지 않습니다. 단지 "로큰롤을 준비합니다. 프로그램은 갈 준비가되어 있지만 아직 시작되지 않았습니다. "

그러나 모든 것에 예외가 있습니다. 추적을 켜고 select *를 scott.emp FOR UPDATE에서 수행하십시오. 그것은 선택이지만, 업데이트입니다. 실행 중에 작업이 완료되고 가져 오기 단계가 수행됩니다. 실행 중에 수행 된 작업은 으로 나가서 모든 행을 터치하고 잠그는 작업이었습니다. 단계를 수행하는 동안 완료된 작업은 나가서 데이터를 클라이언트 에 다시 가져 오는 작업이었습니다.

3) fetch - 이것은 업데이트에서 가져 오지 않기 때문에 SELECTS 에 대한 거의 모든 작업을 볼 수 있습니다 (실제로 다른 DMLS는 없습니다).

SELECT를 처리하는 데는 두 가지 방법이 있습니다. 나는 "빠른 반환 쿼리"과 "느린 반환 쿼리"

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:275215756923#39255764276301

디자인이 깊이에서 이것을 설명하지만, 형식의 쿼리 말을 충분하여 효과적인 오라클에서 발췌 한 것입니다 부르는 :

select * from one_billion_row_table;

은 데이터를 복사하지 않으므로 첫 번째 행을 반환하기 전에 마지막으로 행에 액세스 할 필요가 없습니다. 이있는 블록에서 데이터를 가져 오는 것만으로 데이터를 읽습니다. 그러나

폼의 쿼리 unindexed_column 의해 one_billion_row_table 주문 *

선택; 우리는 아마도 첫 번째 행을 반환하기 전에 마지막 행을 읽을 것

(마지막 행 읽기 때문에 잘! 첫 번째 행 가 반환 될 수 있습니다) 우리는 어딘가 (온도, 종류의 영역을 복사해야 할 것 공백)을 먼저 입력하십시오. 첫 번째 쿼리의 경우

당신 경우 :

그것을 (약간의 작업 구문 분석) 1 개 행을 페치 (단지 준비 을 받고, 아니 현실 세계)를 열고 그것을

폐쇄 분석 당신은 것 가져 오기 단계에서 수행되는 매우 작은 작업을 참조하십시오. 우리는 단지 첫 번째 레코드를 반환하기 위해 하나의 블록을 읽어야합니다.

그러나 두 번째 쿼리와 동일한 단계를 수행하면 첫 번째 행을 반환하기 전에 마지막 행인 을 찾아야하기 때문에 단일 행을 가져 오면 많은 작업이 수행됩니다.

관련 문제