2013-06-19 37 views
11

Tôi đang chạy một truy vấn lớn trong một kịch bản python chống lại cơ sở dữ liệu Postgres tôi sử dụng psycopg2 (tôi nâng cấp lên phiên bản 2.5). Sau khi truy vấn kết thúc, tôi đóng con trỏ và kết nối, và thậm chí chạy gc, nhưng quá trình vẫn tiêu thụ một tấn bộ nhớ (chính xác là 7.3gb). Tôi có bỏ lỡ bước dọn dẹp không?psycopg2 bộ nhớ bị rò rỉ sau khi truy vấn lớn

import psycopg2 
conn = psycopg2.connect("dbname='dbname' user='user' host='host'") 
cursor = conn.cursor() 
cursor.execute("""large query""") 
rows = cursor.fetchall() 
del rows 
cursor.close() 
conn.close() 
import gc 
gc.collect() 

Trả lời

8

Hãy xem câu trả lời tiếp theo bởi @joeblog cho các giải pháp tốt hơn.


Trước tiên, bạn không cần tất cả RAM đó ngay từ đầu. Những gì bạn nên làm ở đây là tìm nạp khối của tập hợp kết quả. Đừng làm một số fetchall(). Thay vào đó, hãy sử dụng phương thức cursor.fetchmany hiệu quả hơn nhiều. Xem the psycopg2 documentation.

Bây giờ, lời giải thích cho lý do tại sao nó không được trả tự do, và lý do tại sao đó không phải là một rò rỉ bộ nhớ trong việc sử dụng chính thức đúng đắn về thuật ngữ đó.

Hầu hết các quá trình không giải phóng bộ nhớ trở lại hệ điều hành khi nó được giải phóng, họ chỉ làm cho nó có sẵn để tái sử dụng ở những nơi khác trong chương trình.

Memory chỉ có thể được phát hành cho hệ điều hành nếu chương trình có thể nhỏ gọn các đối tượng còn lại nằm rải rác trong bộ nhớ. Điều này chỉ có thể nếu các tham chiếu xử lý gián tiếp được sử dụng, vì nếu không di chuyển một đối tượng sẽ làm mất hiệu lực các con trỏ hiện có đối với đối tượng. Tài liệu tham khảo gián tiếp là khá kém hiệu quả, đặc biệt là trên các CPU hiện đại, nơi các con trỏ tìm kiếm xung quanh thực hiện những điều kinh khủng.

Điều gì thường xảy ra khi không có sự chú ý của chương trình là mỗi đoạn bộ nhớ lớn được phân bổ với brk() đáp ứng một vài phần nhỏ vẫn đang được sử dụng.

Hệ điều hành không thể nói cho dù chương trình xem xét bộ nhớ này vẫn được sử dụng hay không, vì vậy nó không thể chỉ tuyên bố nó trở lại. Vì chương trình không có xu hướng truy cập vào bộ nhớ nên hệ điều hành thường sẽ hoán đổi nó theo thời gian, giải phóng bộ nhớ vật lý cho các ứng dụng khác. Đây là một trong những lý do bạn nên có không gian trao đổi.

Có thể viết chương trình mà tay bộ nhớ trở lại hệ điều hành, nhưng tôi không chắc chắn rằng bạn có thể làm điều đó với Python.

Xem thêm:

Vì vậy: đây không phải là thực sự là một bộ nhớ bị rò rỉ . Nếu bạn làm điều gì đó khác sử dụng rất nhiều bộ nhớ, quá trình này sẽ không phát triển nhiều nếu ở tất cả, nó sẽ tái sử dụng bộ nhớ giải phóng trước đó từ phân bổ lớn cuối cùng.

+0

Cảm ơn! Xác nhận rằng bộ nhớ được tái sử dụng bằng cách chạy mã trên hai lần trong cùng một quá trình. Bộ nhớ không tăng trong lần chạy thứ hai. –

+3

Trong khi tất cả mọi thứ nói ở đây là đúng, một kết quả truy vấn bình thường sẽ được chuyển hoàn toàn trên phía máy khách (không phải bởi 'fetch *()' mà bởi 'execute()'). Vì vậy, trong khi sử dụng 'fetchmany()' thay vì 'fetchall()' có thể tiết kiệm một số bộ nhớ về tạo đối tượng Python, sử dụng con trỏ phía máy chủ như được đề xuất bởi @joeblog là giải pháp đúng. – piro

27

Tôi gặp phải một vấn đề tương tự và sau một vài giờ máu, mồ hôi và nước mắt, tìm thấy câu trả lời chỉ đơn giản là yêu cầu bổ sung một tham số.

Thay vì

cursor = conn.cursor() 

ghi

cursor = conn.cursor(name="my_cursor_name") 

hoặc đơn giản hơn nhưng

cursor = conn.cursor("my_cursor_name") 

Các chi tiết được tìm thấy tại http://initd.org/psycopg/docs/usage.html#server-side-cursors

Tôi tìm thấy hướng dẫn một chút bối rối trong đó tôi mặc dù tôi cần phải viết lại SQL của tôi để bao gồm "DECLARE my_cursor_name ...." và sau đó một "FETCH đếm 2000 FROM my_cursor_name" nhưng nó quay ra psycopg hiện tất cả cho bạn dưới mui xe nếu bạn chỉ cần ghi đè tham số mặc định "name = None" khi tạo con trỏ.

Đề xuất ở trên sử dụng fetchone hoặc fetchmany không giải quyết được sự cố vì nếu bạn không đặt tên tham số, psycopg sẽ mặc định cố tải toàn bộ truy vấn vào ram. Điều duy nhất bạn có thể cần phải (ngoài việc tuyên bố một tham số tên) là thay đổi thuộc tính cursor.itersize từ mặc định 2000 thành 1000 nếu bạn vẫn còn quá ít bộ nhớ.

+0

tôi không thể tìm thấy bất cứ điều gì trong 'sqlalchemy' đã giúp tôi tránh được vấn đề OOM, nhưng giải pháp này làm việc cho tôi. cảm ơn bạn! –

7

Joeblog có câu trả lời đúng. Cách bạn đối phó với việc tìm nạp là quan trọng nhưng rõ ràng hơn nhiều so với cách bạn phải xác định con trỏ. Đây là một ví dụ đơn giản để minh họa điều này và cung cấp cho bạn một cái gì đó để sao chép-dán để bắt đầu.

import datetime as dt 
import psycopg2 
import sys 
import time 

conPG = psycopg2.connect("dbname='myDearDB'") 
curPG = conPG.cursor('testCursor') 
curPG.itersize = 100000 # Rows fetched at one time from the server 

curPG.execute("SELECT * FROM myBigTable LIMIT 10000000") 
# Warning: curPG.rowcount == -1 ALWAYS !! 
cptLigne = 0 
for rec in curPG: 
    cptLigne += 1 
    if cptLigne % 10000 == 0: 
     print('.', end='') 
     sys.stdout.flush() # To see the progression 
conPG.commit() # Also close the cursor 
conPG.close() 

Như bạn sẽ thấy, chấm đến theo nhóm nhanh chóng, hơn tạm dừng để có được một bộ đệm hàng (itersize), do đó bạn không cần phải sử dụng fetchmany cho hiệu suất. Khi tôi chạy điều này với /usr/bin/time -v, tôi nhận được kết quả trong chưa đầy 3 phút, chỉ sử dụng 200MB RAM (thay vì 60 GB với con trỏ phía máy khách) cho 10 triệu hàng. Máy chủ không cần ram nhiều hơn vì nó sử dụng bảng tạm thời.

Các vấn đề liên quan