Đôi khi chỉ ghi nhật ký SQL đơn giản (được bật thông qua mô-đun ghi nhật ký của python hoặc thông qua đối số echo=True
trên create_engine()
) có thể cho bạn biết thời gian sử dụng. Ví dụ, nếu bạn đăng nhập một cái gì đó ngay sau khi một hoạt động SQL, bạn sẽ thấy một cái gì đó như thế này trong nhật ký của mình:
17:37:48,325 INFO [sqlalchemy.engine.base.Engine.0x...048c] SELECT ...
17:37:48,326 INFO [sqlalchemy.engine.base.Engine.0x...048c] {<params>}
17:37:48,660 DEBUG [myapp.somemessage]
nếu bạn đăng nhập myapp.somemessage
ngay sau khi phẫu thuật, bạn biết điều đó mất 334ms để hoàn thành phần SQL của nhiều thứ.
Ghi nhật ký SQL cũng sẽ minh họa nếu hàng chục/hàng trăm truy vấn đang được phát hành có thể được tổ chức tốt hơn thành ít truy vấn hơn thông qua kết nối. Khi sử dụng tính năng SQLAlchemy ORM, tính năng "háo hức tải" được cung cấp một phần (contains_eager()
) hoặc hoàn toàn (eagerload()
, eagerload_all()
) tự động hóa hoạt động này, nhưng không có ORM nó chỉ có nghĩa là sử dụng kết nối để có thể tải kết quả trên nhiều bảng một kết quả được đặt thay vì nhân số lượng truy vấn khi có thêm chiều sâu (ví dụ: r + r*r2 + r*r2*r3
...)
Nếu đăng nhập cho thấy các truy vấn riêng lẻ mất quá nhiều thời gian, bạn cần phân tích số lượng thời gian đã sử dụng trong cơ sở dữ liệu xử lý truy vấn, gửi kết quả qua mạng, đang được xử lý bởi DBAPI và cuối cùng được nhận bởi tập hợp kết quả của SQLAlchemy và/hoặc lớp ORM. Mỗi giai đoạn này có thể trình bày các nút cổ chai cá nhân của riêng mình, tùy thuộc vào từng chi tiết cụ thể.
Đối với điều đó bạn cần sử dụng hồ sơ, chẳng hạn như cProfile hoặc hotshot.Dưới đây là một trang trí tôi sử dụng:
import cProfile as profiler
import gc, pstats, time
def profile(fn):
def wrapper(*args, **kw):
elapsed, stat_loader, result = _profile("foo.txt", fn, *args, **kw)
stats = stat_loader()
stats.sort_stats('cumulative')
stats.print_stats()
# uncomment this to see who's calling what
# stats.print_callers()
return result
return wrapper
def _profile(filename, fn, *args, **kw):
load_stats = lambda: pstats.Stats(filename)
gc.collect()
began = time.time()
profiler.runctx('result = fn(*args, **kw)', globals(), locals(),
filename=filename)
ended = time.time()
return ended - began, load_stats, locals()['result']
Để cấu hình một phần của mã, đặt nó trong một hàm với trang trí:
@profile
def go():
return Session.query(FooClass).filter(FooClass.somevalue==8).all()
myfoos = go()
Sản lượng của hồ sơ có thể được sử dụng để đưa ra một ý tưởng mà thời gian đang được chi tiêu. Ví dụ: nếu bạn thấy tất cả thời gian được sử dụng trong phạm vi cursor.execute()
, đó là cuộc gọi DBAPI cấp thấp tới cơ sở dữ liệu và điều đó có nghĩa là truy vấn của bạn phải được tối ưu hóa bằng cách thêm chỉ mục hoặc tái cơ cấu truy vấn và/hoặc lược đồ cơ bản. Đối với nhiệm vụ đó, tôi khuyên bạn nên sử dụng pgadmin cùng với tiện ích EXPLAIN đồ họa của nó để xem loại công việc mà truy vấn đang làm. Nếu bạn thấy hàng nghìn cuộc gọi liên quan đến tìm nạp hàng, điều đó có thể có nghĩa là truy vấn của bạn đang trả về nhiều hàng hơn dự kiến - một sản phẩm Descartes do kết nối không đầy đủ có thể gây ra sự cố này. Tuy nhiên, một vấn đề khác là thời gian trong xử lý kiểu - một loại SQLAlchemy như Unicode
sẽ thực hiện mã hóa/giải mã chuỗi trên các tham số ràng buộc và các cột kết quả, có thể không cần thiết trong mọi trường hợp.
Kết quả của tiểu sử có thể hơi khó khăn nhưng sau một số thực hành, chúng rất dễ đọc. Đã từng có ai đó trong danh sách gửi thư tuyên bố chậm và sau khi anh ấy đăng kết quả hồ sơ, tôi có thể chứng minh rằng các vấn đề về tốc độ là do độ trễ của mạng - thời gian sử dụng trong cursor.execute() cũng như tất cả Python các phương thức rất nhanh, trong khi phần lớn thời gian được dành cho socket.receive().
Nếu bạn đang cảm thấy tham vọng, cũng có một ví dụ liên quan hơn về lược tả SQLAlchemy trong các bài kiểm tra đơn vị SQLAlchemy, nếu bạn poke xung quanh http://www.sqlalchemy.org/trac/browser/sqlalchemy/trunk/test/aaa_profiling. Ở đó, chúng tôi có các thử nghiệm sử dụng các trình trang trí xác nhận số lượng cuộc gọi phương thức tối đa được sử dụng cho các hoạt động cụ thể, do đó nếu một điều gì đó không hiệu quả được kiểm tra, các kiểm tra sẽ tiết lộ nó (điều quan trọng cần lưu ý là trong Python, các cuộc gọi hàm có giá trị cao nhất chi phí của bất kỳ hoạt động nào và số lượng cuộc gọi thường xuyên hơn là không tỷ lệ thuận với thời gian sử dụng). Đáng chú ý là các bài kiểm tra "zoomark" sử dụng lược đồ "bắt giữ SQL" ưa thích để cắt giảm chi phí của DBAPI khỏi phương trình - mặc dù kỹ thuật đó không thực sự cần thiết cho việc lập hồ sơ vườn.
Nếu bạn đang sử dụng Flask-SQLAlchemy, hãy thêm 'SQLALCHEMY_ECHO = True' vào cấu hình ứng dụng của bạn. – henrycjc