SQLAlchemy: Tạo so với sử dụng lại một phiên


98

Chỉ là một câu hỏi nhanh: SQLAlchemy nói về việc gọi sessionmaker()một lần nhưng gọi Session()lớp kết quả mỗi khi bạn cần nói chuyện với DB của mình. Đối với tôi, điều đó có nghĩa là lần thứ hai tôi sẽ làm lần đầu tiên session.add(x)hoặc điều gì đó tương tự, lần đầu tiên tôi sẽ làm

from project import Session
session = Session()

Những gì tôi đã làm cho đến bây giờ là thực hiện cuộc gọi session = Session()trong mô hình của mình một lần và sau đó luôn nhập cùng một phiên vào bất kỳ đâu trong ứng dụng của tôi. Vì đây là một ứng dụng web nên điều này thường có nghĩa giống nhau (khi một chế độ xem được thực thi).

Nhưng sự khác biệt là ở đâu? Bất lợi của việc sử dụng một phiên mọi lúc so với việc sử dụng nó cho nội dung cơ sở dữ liệu của tôi cho đến khi chức năng của tôi hoàn thành và sau đó tạo một phiên mới vào lần tiếp theo tôi muốn nói chuyện với DB của mình?

Tôi hiểu rằng nếu tôi sử dụng nhiều chủ đề, mỗi chủ đề sẽ có phiên của riêng mình. Nhưng sử dụng scoped_session(), tôi đã chắc chắn rằng vấn đề đó không tồn tại, phải không?

Vui lòng làm rõ nếu bất kỳ giả định nào của tôi là sai.

Câu trả lời:


223

sessionmaker()là một nhà máy, nó ở đó để khuyến khích đặt các tùy chọn cấu hình để tạo các Sessionđối tượng mới chỉ ở một nơi. Nó là tùy chọn, ở chỗ bạn có thể dễ dàng gọi Session(bind=engine, expire_on_commit=False)bất cứ lúc nào bạn cần một cái mới Session, ngoại trừ sự dài dòng và dư thừa của nó, và tôi muốn ngăn chặn sự gia tăng của những "người trợ giúp" quy mô nhỏ mà mỗi người đều tiếp cận vấn đề dư thừa này trong một số và cách khó hiểu hơn.

Vì vậy, sessionmaker()chỉ là một công cụ giúp bạn tạo ra Sessioncác đối tượng khi bạn cần.

Phần tiếp theo. Tôi nghĩ câu hỏi đặt ra là, sự khác biệt giữa việc tạo ra một cái mới Session()ở nhiều điểm khác nhau so với việc chỉ sử dụng một cái suốt đời. Câu trả lời, không nhiều lắm. Sessionlà một vùng chứa cho tất cả các đối tượng mà bạn đặt vào đó và sau đó nó cũng theo dõi một giao dịch đang mở. Tại thời điểm bạn gọi rollback()hoặc commit(), giao dịch đã kết thúc và Sessionkhông có kết nối với cơ sở dữ liệu cho đến khi nó được gọi để phát ra SQL một lần nữa. Các liên kết mà nó nắm giữ với các đối tượng được ánh xạ của bạn là tham chiếu yếu, miễn là các đối tượng sạch sẽ với các thay đổi đang chờ xử lý, vì vậy ngay cả về mặt đó, nó Sessionsẽ tự trống trở lại trạng thái hoàn toàn mới khi ứng dụng của bạn mất tất cả các tham chiếu đến các đối tượng được ánh xạ. Nếu bạn để mặc định"expire_on_commit"thiết lập, sau đó tất cả các đối tượng sẽ hết hạn sau một cam kết. Nếu điều đó Sessionbị treo trong năm hoặc hai mươi phút và tất cả các loại thứ đã thay đổi trong cơ sở dữ liệu vào lần tiếp theo bạn sử dụng nó, nó sẽ tải tất cả trạng thái hoàn toàn mới vào lần tiếp theo bạn truy cập các đối tượng đó mặc dù chúng đã được lưu trong bộ nhớ. trong hai mươi phút.

Trong các ứng dụng web, chúng ta thường nói, tại sao bạn không tạo một thương hiệu mới Sessioncho mỗi yêu cầu, thay vì sử dụng lặp đi lặp lại cùng một thương hiệu . Thực hành này đảm bảo rằng yêu cầu mới bắt đầu "sạch". Nếu một số đối tượng từ yêu cầu trước đó vẫn chưa được thu dọn và nếu có thể bạn đã tắt "expire_on_commit", có thể một số trạng thái từ yêu cầu trước vẫn còn tồn tại và trạng thái đó thậm chí có thể đã khá cũ. Nếu bạn cẩn thận expire_on_commitbật và chắc chắn kết thúc cuộc gọi commit()hoặc rollback()theo yêu cầu thì không sao cả, nhưng nếu bạn bắt đầu với một thương hiệu mới Session, thì thậm chí không có bất kỳ câu hỏi nào cho thấy bạn đang bắt đầu sạch sẽ. Vì vậy, ý tưởng bắt đầu mỗi yêu cầu bằng mộtSessionthực sự chỉ là cách đơn giản nhất để đảm bảo rằng bạn đang bắt đầu mới và làm cho việc sử dụng expire_on_commitkhá nhiều tùy chọn, vì cờ này có thể phát sinh thêm rất nhiều SQL cho một hoạt động gọi commit()ở giữa một chuỗi hoạt động. Không chắc chắn nếu điều này trả lời câu hỏi của bạn.

Vòng tiếp theo là những gì bạn đề cập về luồng. Nếu ứng dụng của bạn là đa luồng, chúng tôi khuyên bạn nên đảm bảo rằng ứng Sessiondụng đang sử dụng là cục bộ cho ... thứ gì đó. scoped_session()theo mặc định làm cho nó cục bộ thành luồng hiện tại. Trong một ứng dụng web, cục bộ cho yêu cầu trên thực tế thậm chí còn tốt hơn. Flask-SQLAlchemy thực sự gửi một "chức năng phạm vi" tùy chỉnh để scoped_session()bạn nhận được một phiên theo phạm vi yêu cầu. Ứng dụng Kim tự tháp trung bình gắn Phiên vào sổ đăng ký "yêu cầu". Khi sử dụng các lược đồ như thế này, ý tưởng "tạo Phiên mới khi bắt đầu theo yêu cầu" tiếp tục giống như cách đơn giản nhất để giữ mọi thứ suôn sẻ.


17
Chà, điều này trả lời tất cả các câu hỏi của tôi về phần SQLAlchemy và thậm chí thêm một số thông tin về Flask và Pyramid! Thêm phần thưởng: nhà phát triển trả lời;) Tôi ước tôi có thể bỏ phiếu nhiều hơn một lần. Cảm ơn rât nhiều!
javex

Một sự làm rõ, nếu có thể: bạn nói expire_on_commit "có thể phát sinh thêm nhiều SQL" ... bạn có thể cho biết thêm chi tiết không? Tôi nghĩ expire_on_commit chỉ quan tâm đến những gì xảy ra trong RAM, không phải những gì xảy ra trong cơ sở dữ liệu.
Veky

3
expire_on_commit có thể dẫn đến nhiều SQL hơn nếu bạn sử dụng lại cùng một Phiên và một số đối tượng vẫn tồn tại trong Phiên đó, khi bạn truy cập chúng, bạn sẽ nhận được một CHỌN đơn hàng cho mỗi một trong số chúng khi chúng làm mới riêng lẻ trạng thái của họ trong điều kiện của giao dịch mới.
zzzeek

1
Xin chào, @zzzeek. Cảm ơn vì câu trả lời xuất sắc. Tôi rất mới trong python và một số điều tôi muốn làm rõ: 1) Tôi có hiểu đúng không khi tôi tạo "phiên" mới bằng cách gọi phương thức Session (), nó sẽ tạo Giao dịch SQL, sau đó giao dịch sẽ được mở cho đến khi tôi cam kết / phiên quay lại ? 2) Phiên () có sử dụng một số loại nhóm kết nối hoặc tạo kết nối mới đến sql mỗi lần không?
Alex Gurskiy

27

Ngoài câu trả lời tuyệt vời của zzzeek, ​​đây là một công thức đơn giản để nhanh chóng tạo các phiên tự động, bổ sung:

from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

@contextmanager
def db_session(db_url):
    """ Creates a context with an open SQLAlchemy session.
    """
    engine = create_engine(db_url, convert_unicode=True)
    connection = engine.connect()
    db_session = scoped_session(sessionmaker(autocommit=False, autoflush=True, bind=engine))
    yield db_session
    db_session.close()
    connection.close()

Sử dụng:

from mymodels import Foo

with db_session("sqlite://") as db:
    foos = db.query(Foo).all()

3
Có lý do gì khiến bạn không chỉ tạo một phiên mới mà còn tạo một kết nối mới không?
danqing

Không hẳn - đây là một ví dụ nhanh để chỉ ra cơ chế, mặc dù việc tạo ra mọi thứ mới mẻ trong quá trình thử nghiệm là rất hợp lý, nơi tôi sử dụng phương pháp này nhiều nhất. Sẽ dễ dàng mở rộng hàm này với kết nối như một đối số tùy chọn.
Berislav Lopac
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.