SQLAlchemy: การสร้างเทียบกับการนำเซสชันกลับมาใช้ใหม่


104

เพียงแค่คำถามสั้น ๆ : SQLAlchemy พูดถึงการโทรsessionmaker()ครั้งเดียว แต่เรียกSession()คลาสผลลัพธ์ทุกครั้งที่คุณต้องคุยกับฐานข้อมูลของคุณ สำหรับฉันนั่นหมายถึงครั้งที่สองที่ฉันจะทำสิ่งแรกsession.add(x)หรือสิ่งที่คล้ายกันฉันจะทำก่อน

from project import Session
session = Session()

สิ่งที่ฉันทำจนถึงตอนนี้คือโทรออกsession = Session()ในโมเดลของฉันหนึ่งครั้งจากนั้นนำเข้าเซสชันเดียวกันทุกที่ในแอปพลิเคชัน เนื่องจากนี่เป็นเว็บแอปพลิเคชันจึงมักจะมีความหมายเหมือนกัน (เนื่องจากมีการเรียกใช้มุมมองเดียว)

แต่ความแตกต่างอยู่ที่ไหน? อะไรคือข้อเสียของการใช้หนึ่งเซสชันตลอดเวลากับการใช้งานสำหรับข้อมูลในฐานข้อมูลของฉันจนกว่าฟังก์ชันของฉันจะเสร็จสิ้นแล้วจึงสร้างใหม่ในครั้งต่อไปที่ฉันต้องการพูดคุยกับฐานข้อมูลของฉัน

ฉันเข้าใจว่าถ้าฉันใช้หลายเธรดแต่ละเธรดควรมีเซสชันของตัวเอง แต่เมื่อใช้scoped_session()ฉันแน่ใจแล้วว่าไม่มีปัญหาใช่ไหม

โปรดชี้แจงหากข้อสันนิษฐานของฉันผิด

คำตอบ:


236

sessionmaker()เป็นโรงงานที่สนับสนุนให้วางตัวเลือกการกำหนดค่าสำหรับการสร้างSessionวัตถุใหม่ในที่เดียว เป็นทางเลือกที่คุณสามารถโทรหาได้ง่าย ๆSession(bind=engine, expire_on_commit=False)เมื่อใดก็ตามที่คุณต้องการสิ่งใหม่Sessionยกเว้นว่ามันใช้งานได้และซ้ำซ้อนและฉันต้องการหยุดการแพร่กระจายของ "ตัวช่วย" ขนาดเล็กที่แต่ละคนเข้าหาปัญหาของความซ้ำซ้อนนี้ในใหม่ และวิธีที่สับสนมากขึ้น

ดังนั้นsessionmaker()เป็นเพียงเครื่องมือที่จะช่วยคุณสร้างSessionวัตถุเมื่อคุณต้องการ

ส่วนถัดไป ฉันคิดว่าคำถามคืออะไรคือความแตกต่างระหว่างการสร้างสิ่งใหม่Session()ในหลาย ๆ จุดกับการใช้เพียงครั้งเดียวตลอดทาง คำตอบไม่มาก Sessionเป็นคอนเทนเนอร์สำหรับวัตถุทั้งหมดที่คุณใส่ลงไปจากนั้นก็จะติดตามธุรกรรมที่เปิดอยู่ ในขณะที่คุณโทรrollback()หรือcommit()ธุรกรรมสิ้นสุดลงและSessionไม่มีการเชื่อมต่อกับฐานข้อมูลจนกว่าจะถูกเรียกให้ปล่อย SQL อีกครั้ง ลิงก์ที่เก็บไว้กับอ็อบเจ็กต์ที่แมปของคุณมีการอ้างอิงที่อ่อนแอหากอ็อบเจ็กต์นั้นสะอาดจากการเปลี่ยนแปลงที่รอดำเนินการดังนั้นแม้ว่าในกรณีนี้Sessionจะทำให้ตัวเองว่างเปล่ากลับสู่สถานะใหม่ล่าสุดเมื่อแอปพลิเคชันของคุณสูญเสียการอ้างอิงทั้งหมดไปยังอ็อบเจ็กต์ที่แมป หากคุณปล่อยให้เป็นค่าเริ่มต้น"expire_on_commit"จากนั้นวัตถุทั้งหมดจะหมดอายุหลังจากการคอมมิต หากสิ่งนั้นSessionค้างอยู่เป็นเวลาห้าหรือยี่สิบนาทีและมีการเปลี่ยนแปลงทุกอย่างในฐานข้อมูลในครั้งต่อไปที่คุณใช้งานมันจะโหลดสถานะใหม่ทั้งหมดในครั้งต่อไปที่คุณเข้าถึงวัตถุเหล่านั้นแม้ว่าพวกเขาจะอยู่ในหน่วยความจำก็ตาม เป็นเวลายี่สิบนาที

ในเว็บแอปพลิเคชันเรามักจะพูดว่าเฮ้ทำไมคุณไม่สร้างแบรนด์ใหม่Sessionสำหรับแต่ละคำขอแทนที่จะใช้อันเดิมซ้ำแล้วซ้ำเล่า แนวทางปฏิบัตินี้ช่วยให้มั่นใจได้ว่าคำขอใหม่เริ่ม "สะอาด" หากวัตถุบางอย่างจากคำขอก่อนหน้านี้ยังไม่ได้รับการเก็บรวบรวมขยะและหากคุณปิดไป"expire_on_commit"แล้วบางทีสถานะบางอย่างจากคำขอก่อนหน้านี้ยังคงค้างอยู่และสถานะนั้นอาจค่อนข้างเก่า หากคุณระมัดระวังที่จะexpire_on_commitเปิดเครื่องทิ้งไว้และโทรออกอย่างแน่นอนcommit()หรือrollback()เมื่อขอสิ้นสุดก็ไม่เป็นไร แต่ถ้าคุณเริ่มต้นด้วยแบรนด์ใหม่Sessionก็ไม่มีแม้แต่คำถามใด ๆ ที่คุณกำลังเริ่มต้นใหม่ ดังนั้นความคิดที่จะเริ่มต้นแต่ละคำขอด้วยไฟล์Sessionเป็นเพียงวิธีที่ง่ายที่สุดในการตรวจสอบให้แน่ใจว่าคุณเริ่มต้นใหม่และใช้ประโยชน์จากexpire_on_commitทางเลือกได้มากเนื่องจากแฟล็กนี้อาจมี SQL เพิ่มเติมจำนวนมากสำหรับการดำเนินการที่เรียกcommit()อยู่ระหว่างชุดการดำเนินการ ไม่แน่ใจว่าจะตอบคำถามของคุณได้หรือไม่

รอบต่อไปคือสิ่งที่คุณพูดถึงเกี่ยวกับเธรด หากแอปของคุณเป็นแบบมัลติเธรดเราขอแนะนำให้ตรวจสอบว่าการSessionใช้งานอยู่ในพื้นที่ของ ... scoped_session()โดยค่าเริ่มต้นจะทำให้เป็นแบบโลคัลสำหรับเธรดปัจจุบัน ในแอปพลิเคชันเว็บในท้องถิ่นตามคำขอนั้นดีกว่าในความเป็นจริง Flask-SQLAlchemy จะส่ง "ฟังก์ชันขอบเขต" ที่กำหนดเองไปscoped_session()เพื่อให้คุณได้รับเซสชันที่กำหนดขอบเขตคำขอ แอปพลิเคชัน Pyramid โดยเฉลี่ยจะยึดเซสชันไว้ในรีจิสทรี "คำขอ" เมื่อใช้รูปแบบเช่นนี้แนวคิด "สร้างเซสชันใหม่ตามคำขอเริ่มต้น" ยังคงดูเหมือนวิธีที่ตรงไปตรงมาที่สุดในการทำให้สิ่งต่างๆตรงไปตรงมา


18
ว้าวนี่ตอบทุกคำถามของฉันในส่วน SQLAlchemy และยังเพิ่มข้อมูลบางอย่างเกี่ยวกับ Flask และ Pyramid! โบนัสที่เพิ่มเข้ามา: นักพัฒนาตอบ;) ฉันหวังว่าฉันจะโหวตได้มากกว่าหนึ่งครั้ง ขอบคุณมาก!
javex

คำชี้แจงอย่างหนึ่งถ้าเป็นไปได้: คุณบอกว่า expire_on_commit "อาจมี SQL เพิ่มเติมจำนวนมาก" ... คุณสามารถให้รายละเอียดเพิ่มเติมได้หรือไม่? ฉันคิดว่า expire_on_commit เกี่ยวข้องกับสิ่งที่เกิดขึ้นใน RAM เท่านั้นไม่ใช่สิ่งที่เกิดขึ้นในฐานข้อมูล
Veky

3
expire_on_commit อาจส่งผลให้มี SQL มากขึ้นหากคุณใช้เซสชันเดิมซ้ำอีกครั้งและวัตถุบางอย่างยังคงแขวนอยู่ในเซสชันนั้นเมื่อคุณเข้าถึงคุณจะได้รับ SELECT แถวเดียวสำหรับแต่ละรายการเมื่อรีเฟรชทีละแถว สถานะของพวกเขาในแง่ของธุรกรรมใหม่
zzzeek

1
สวัสดี @zzzeek ขอบคุณสำหรับคำตอบที่ยอดเยี่ยม ฉันใหม่มากใน python และหลายสิ่งที่ฉันอยากจะชี้แจง: 1) ฉันเข้าใจถูกต้องหรือไม่เมื่อฉันสร้าง "เซสชัน" ใหม่โดยเรียกเมธอด Session () ซึ่งจะสร้างธุรกรรม SQL จากนั้นธุรกรรมจะถูกเปิดจนกว่าฉันจะกระทำ / ย้อนเซสชัน เหรอ? 2) เซสชั่น () ใช้พูลการเชื่อมต่อบางประเภทหรือทำการเชื่อมต่อใหม่กับ sql ทุกครั้งหรือไม่?
Alex Gurskiy

28

นอกเหนือจากคำตอบที่ยอดเยี่ยมของ zzzeek แล้วนี่คือสูตรง่ายๆในการสร้างเซสชันที่ถูกปิดล้อมด้วยตัวเอง

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()

การใช้งาน:

from mymodels import Foo

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

3
มีเหตุผลไหมที่คุณไม่เพียงสร้างเซสชันใหม่ แต่เป็นการเชื่อมต่อใหม่ด้วย
danqing

ไม่จริง - นี่เป็นตัวอย่างสั้น ๆ ในการแสดงกลไกแม้ว่ามันจะสมเหตุสมผลที่จะสร้างทุกอย่างใหม่ในการทดสอบซึ่งฉันใช้แนวทางนี้มากที่สุด มันควรจะง่ายที่จะขยายฟังก์ชันนี้โดยใช้การเชื่อมต่อเป็นอาร์กิวเมนต์เสริม
Berislav Lopac
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.