เมื่อใดควรปิดเคอร์เซอร์โดยใช้ MySQLdb


86

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

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

คำตอบ:


80

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

ในเวอร์ชัน 1.2.5 ของโมดูลMySQLdb.Connectionใช้โปรโตคอลตัวจัดการบริบทด้วยรหัสต่อไปนี้ ( github ):

def __enter__(self):
    if self.get_autocommit():
        self.query("BEGIN")
    return self.cursor()

def __exit__(self, exc, value, tb):
    if exc:
        self.rollback()
    else:
        self.commit()

มีคำถามและคำตอบที่มีwithอยู่มากมายเกี่ยวกับอยู่แล้วหรือคุณสามารถอ่านทำความเข้าใจกับคำสั่ง "ด้วย" ของ Pythonแต่โดยพื้นฐานแล้วสิ่งที่เกิดขึ้นคือ__enter__ดำเนินการเมื่อเริ่มต้นwithบล็อกและ__exit__ดำเนินการเมื่อออกจากwithบล็อก คุณสามารถใช้ไวยากรณ์ที่เป็นทางเลือกwith EXPR as VARเพื่อผูกอ็อบเจ็กต์ที่ส่งคืนด้วย __enter__ชื่อหากคุณต้องการอ้างอิงอ็อบเจ็กต์นั้นในภายหลัง ดังนั้นจากการใช้งานข้างต้นนี่เป็นวิธีง่ายๆในการสืบค้นฐานข้อมูลของคุณ:

connection = MySQLdb.connect(...)
with connection as cursor:            # connection.__enter__ executes at this line
    cursor.execute('select 1;')
    result = cursor.fetchall()        # connection.__exit__ executes after this line
print result                          # prints "((1L,),)"

คำถามคือสถานะของการเชื่อมต่อและเคอร์เซอร์หลังจากออกจากwithบล็อกคืออะไร? __exit__วิธีการแสดงข้างต้นสายเท่านั้นself.rollback()หรือself.commit()และไม่วิธีการเหล่านั้นไปในการที่จะเรียกclose()วิธีการ เคอร์เซอร์เองไม่ได้__exit__กำหนดวิธีการไว้ - และจะไม่สำคัญว่าจะเป็นอย่างไรเพราะwithเป็นเพียงการจัดการการเชื่อมต่อเท่านั้น ดังนั้นทั้งการเชื่อมต่อและเคอร์เซอร์จะยังคงเปิดอยู่หลังจากออกจากwithบล็อก สิ่งนี้ยืนยันได้อย่างง่ายดายโดยการเพิ่มรหัสต่อไปนี้ในตัวอย่างด้านบน:

try:
    cursor.execute('select 1;')
    print 'cursor is open;',
except MySQLdb.ProgrammingError:
    print 'cursor is closed;',
if connection.open:
    print 'connection is open'
else:
    print 'connection is closed'

คุณควรเห็นผลลัพธ์ "เคอร์เซอร์เปิดอยู่; การเชื่อมต่อเปิดอยู่" พิมพ์เป็น stdout

ฉันเชื่อว่าคุณต้องปิดเคอร์เซอร์ก่อนที่จะทำการเชื่อมต่อ

ทำไม? MySQL C APIซึ่งเป็นพื้นฐานสำหรับการMySQLdbไม่ใช้วัตถุเคอร์เซอร์ใด ๆ ตามที่ระบุไว้ในเอกสารโมดูล: "MySQL ไม่สนับสนุนเคอร์เซอร์อย่างไรก็ตามเคอร์เซอร์เป็นเทิดทูนอย่างง่ายดาย." อันที่จริงMySQLdb.cursors.BaseCursorคลาสนี้สืบทอดมาโดยตรงobjectและไม่มีข้อ จำกัด ดังกล่าวสำหรับเคอร์เซอร์ที่เกี่ยวข้องกับการกระทำ / ย้อนกลับ นักพัฒนา Oracle กล่าวว่า :

cnx.commit () ก่อน cur.close () ฟังดูสมเหตุสมผลที่สุดสำหรับฉัน บางทีคุณอาจทำตามกฎ: "ปิดเคอร์เซอร์ถ้าคุณไม่ต้องการอีกต่อไป" ดังนั้นกระทำ () ก่อนปิดเคอร์เซอร์ ในท้ายที่สุดสำหรับ Connector / Python ก็ไม่ได้สร้างความแตกต่างมากนัก แต่หรือฐานข้อมูลอื่น ๆ อาจ

ฉันคาดหวังว่าจะใกล้เคียงที่สุดเท่าที่คุณจะเข้าสู่ "แนวปฏิบัติมาตรฐาน" ในเรื่องนี้

มีข้อได้เปรียบที่สำคัญในการค้นหาชุดธุรกรรมที่ไม่ต้องใช้การคอมมิชชันกลางเพื่อที่คุณจะได้ไม่ต้องรับเคอร์เซอร์ใหม่สำหรับแต่ละธุรกรรมหรือไม่?

ฉันสงสัยมากและในการพยายามทำเช่นนั้นคุณอาจแนะนำข้อผิดพลาดของมนุษย์เพิ่มเติม ดีกว่าที่จะตัดสินใจเกี่ยวกับอนุสัญญาและยึดติดกับมัน

มีค่าใช้จ่ายจำนวนมากสำหรับการรับเคอร์เซอร์ใหม่หรือไม่ใช่เรื่องใหญ่?

ค่าโสหุ้ยมีค่าเล็กน้อยและไม่ได้สัมผัสกับเซิร์ฟเวอร์ฐานข้อมูลเลย ทั้งหมดอยู่ในการใช้งาน MySQLdb คุณสามารถดูBaseCursor.__init__ใน github ได้หากคุณอยากรู้จริงๆว่าเกิดอะไรขึ้นเมื่อคุณสร้างเคอร์เซอร์ใหม่

ย้อนกลับไปก่อนหน้านี้ตอนที่เรากำลังคุยกันwithบางทีตอนนี้คุณสามารถเข้าใจได้แล้วว่าทำไมMySQLdb.Connectionคลาส__enter__และ__exit__เมธอดจึงทำให้คุณมีเคอร์เซอร์ออบเจ็กต์ใหม่ในทุกๆwithบล็อกและไม่ต้องกังวลกับการติดตามหรือปิดมันในตอนท้ายของบล็อก มีน้ำหนักเบาพอสมควรและมีไว้เพื่อความสะดวกของคุณเท่านั้น

หากคุณจำเป็นต้องจัดการกับวัตถุเคอร์เซอร์แบบไมโครคุณสามารถใช้contextlib.closingเพื่อชดเชยความจริงที่ว่าวัตถุเคอร์เซอร์ไม่มี__exit__วิธีการที่กำหนดไว้ สำหรับเรื่องนั้นคุณยังสามารถใช้เพื่อบังคับให้วัตถุการเชื่อมต่อปิดตัวเองเมื่อออกจากwithบล็อก สิ่งนี้ควรแสดงผล "my_curs is closed; my_conn is closed":

from contextlib import closing
import MySQLdb

with closing(MySQLdb.connect(...)) as my_conn:
    with closing(my_conn.cursor()) as my_curs:
        my_curs.execute('select 1;')
        result = my_curs.fetchall()
try:
    my_curs.execute('select 1;')
    print 'my_curs is open;',
except MySQLdb.ProgrammingError:
    print 'my_curs is closed;',
if my_conn.open:
    print 'my_conn is open'
else:
    print 'my_conn is closed'

โปรดทราบว่า with closing(arg_obj)จะไม่เรียกวัตถุอาร์กิวเมนต์__enter__และ__exit__วิธีการ มันจะเพียงโทรวัตถุอาร์กิวเมนต์ของcloseวิธีการในตอนท้ายของwithบล็อก (หากต้องการเห็นนี้ในการดำเนินการเพียงแค่กำหนดชั้นเรียนFooด้วย__enter__, __exit__และcloseวิธีการที่มีง่ายprintงบและเปรียบเทียบสิ่งที่เกิดขึ้นเมื่อคุณทำwith Foo(): passกับสิ่งที่เกิดขึ้นเมื่อคุณทำwith closing(Foo()): pass.) นี้มีสองผลกระทบอย่างมีนัยสำคัญ:

ขั้นแรกหากเปิดใช้งานโหมด autocommit MySQLdb จะBEGINทำธุรกรรมอย่างชัดเจนบนเซิร์ฟเวอร์เมื่อคุณใช้with connectionและกระทำหรือย้อนกลับธุรกรรมที่ส่วนท้ายของบล็อก สิ่งเหล่านี้เป็นพฤติกรรมเริ่มต้นของ MySQLdb ซึ่งมีไว้เพื่อปกป้องคุณจากพฤติกรรมเริ่มต้นของ MySQL ในการส่งคำสั่ง DML ใด ๆ และทั้งหมดทันที MySQLdb จะถือว่าเมื่อคุณใช้ตัวจัดการบริบทคุณต้องการธุรกรรมและใช้ Explicit BEGINเพื่อข้ามการตั้งค่า autocommit บนเซิร์ฟเวอร์ หากคุณคุ้นเคยกับการใช้with connectionงานคุณอาจคิดว่า autocommit ถูกปิดใช้งานเมื่อมีการข้ามเท่านั้น คุณอาจได้รับความประหลาดใจที่ไม่พึงประสงค์ถ้าคุณเพิ่มclosingรหัสของคุณและสูญเสียความสมบูรณ์ของธุรกรรม คุณจะไม่สามารถย้อนกลับการเปลี่ยนแปลงได้คุณอาจเริ่มเห็นจุดบกพร่องที่เกิดขึ้นพร้อมกันและอาจไม่ชัดเจนในทันทีว่าทำไม

ประการที่สองwith closing(MySQLdb.connect(user, pass)) as VARผูกวัตถุการเชื่อมต่อไปVARในทางตรงกันข้ามกับwith MySQLdb.connect(user, pass) as VARที่ผูกวัตถุเคอร์เซอร์ใหม่VARเพื่อ ในกรณีหลังนี้คุณจะไม่สามารถเข้าถึงวัตถุเชื่อมต่อได้โดยตรง! แต่คุณจะต้องใช้connectionแอตทริบิวต์ของเคอร์เซอร์ซึ่งให้การเข้าถึงพร็อกซีไปยังการเชื่อมต่อเดิม เมื่อเคอร์เซอร์จะปิดมันแอตทริบิวต์มีการตั้งค่าconnection Noneสิ่งนี้ส่งผลให้เกิดการเชื่อมต่อที่ถูกละทิ้งซึ่งจะค้างอยู่จนกว่าจะเกิดเหตุการณ์อย่างใดอย่างหนึ่งต่อไปนี้:

  • การอ้างอิงเคอร์เซอร์ทั้งหมดจะถูกลบออก
  • เคอร์เซอร์อยู่นอกขอบเขต
  • การเชื่อมต่อหมดเวลา
  • การเชื่อมต่อถูกปิดด้วยตนเองผ่านเครื่องมือการดูแลระบบเซิร์ฟเวอร์

คุณสามารถทดสอบสิ่งนี้ได้โดยตรวจสอบการเชื่อมต่อแบบเปิด (ใน Workbench หรือโดยใช้SHOW PROCESSLIST ) ในขณะที่ดำเนินการบรรทัดต่อไปนี้ทีละบรรทัด:

with MySQLdb.connect(...) as my_curs:
    pass
my_curs.close()
my_curs.connection          # None
my_curs.connection.close()  # throws AttributeError, but connection still open
del my_curs                 # connection will close here

15
โพสต์ของคุณละเอียดถี่ถ้วนที่สุด แต่ถึงแม้จะอ่านซ้ำสองสามครั้งฉันก็ยังคงงงงวยเกี่ยวกับการปิดเคอร์เซอร์ เมื่อพิจารณาจากโพสต์จำนวนมากในเรื่องนี้ดูเหมือนว่าจะเป็นจุดที่ทำให้เกิดความสับสน Takeaway ของฉันคือเคอร์เซอร์ดูเหมือนจะไม่ต้องการให้เรียก .close () - เลยทีเดียว ทำไมถึงมีเมธอด. close ()?
SMGreenfield

6
คำตอบสั้น ๆ คือcursor.close()เป็นส่วนหนึ่งของPython DB APIซึ่งไม่ได้เขียนขึ้นโดยเฉพาะโดยคำนึงถึง MySQL
อากาศ

1
เหตุใดการเชื่อมต่อจึงปิดลงหลังจากเดล my_curs
BAE

@ChengchengPei my_cursถือการอ้างอิงครั้งสุดท้ายถึงconnectionวัตถุ เมื่อไม่มีการอ้างอิงนั้นอีกต่อไปconnectionวัตถุควรถูกเก็บรวบรวมขยะ
ออกอากาศ

นี่เป็นคำตอบที่ยอดเยี่ยมขอบคุณ คำอธิบายที่ดีของwithและMySQLdb.Connection's __enter__และ__exit__ฟังก์ชั่น อีกครั้งขอบคุณ @ แอร์
Eugene

33

จะดีกว่าถ้าเขียนใหม่โดยใช้คีย์เวิร์ด "with" "ด้วย" จะดูแลเกี่ยวกับการปิดเคอร์เซอร์ (เป็นสิ่งสำคัญเนื่องจากเป็นทรัพยากรที่ไม่มีการจัดการ) โดยอัตโนมัติ ประโยชน์คือมันจะปิดเคอร์เซอร์ในกรณีที่มีข้อยกเว้นเกินไป

from contextlib import closing
import MySQLdb

''' At the beginning you open a DB connection. Particular moment when
  you open connection depends from your approach:
  - it can be inside the same function where you work with cursors
  - in the class constructor
  - etc
'''
db = MySQLdb.connect("host", "user", "pass", "database")
with closing(db.cursor()) as cur:
    cur.execute("somestuff")
    results = cur.fetchall()
    # do stuff with results

    cur.execute("insert operation")
    # call commit if you do INSERT, UPDATE or DELETE operations
    db.commit()

    cur.execute("someotherstuff")
    results2 = cur.fetchone()
    # do stuff with results2

# at some point when you decided that you do not need
# the open connection anymore you close it
db.close()

ฉันไม่คิดว่าwithนี่เป็นตัวเลือกที่ดีถ้าคุณต้องการใช้ใน Flask หรือเว็บเฟรมเวิร์กอื่น ๆ หากสถานการณ์เป็นเช่นhttp://flask.pocoo.org/docs/patterns/sqlite3/#sqlite3นั้นจะมีปัญหา
James King

@ james-king ฉันไม่ได้ทำงานกับ Flask แต่ในตัวอย่างของคุณ Flask จะปิดการเชื่อมต่อ db เอง จริงๆแล้วในรหัสของฉันฉันใช้วิธีการที่แตกต่างกันเล็กน้อย - ฉันใช้กับเคอร์เซอร์ใกล้ with closing(self.db.cursor()) as cur: cur.execute("UPDATE table1 SET status = %s WHERE id = %s",(self.INTEGR_STATUS_PROCESSING, id)) self.db.commit()
Roman Podlinov

@RomanPodlinov ใช่ถ้าคุณใช้กับเคอร์เซอร์สิ่งต่างๆก็จะดี
James King

7

หมายเหตุ: คำตอบนี้ใช้สำหรับPyMySQLซึ่งเป็นการแทนที่ MySQLdb และ MySQLdb เวอร์ชันล่าสุดอย่างมีประสิทธิภาพเนื่องจาก MySQLdb หยุดการบำรุงรักษา ผมเชื่อว่าทุกอย่างที่นี่เป็นยังที่แท้จริงของ MySQLdb เดิม แต่ยังไม่ได้ตรวจสอบ

ก่อนอื่นข้อเท็จจริงบางประการ:

  • withไวยากรณ์ของ Python เรียกใช้__enter__เมธอดของตัวจัดการบริบทก่อนที่จะเรียกใช้เนื้อหาของwithบล็อกและของมัน__exit__วิธีการในภายหลัง
  • การเชื่อมต่อมี__enter__เมธอดที่ไม่ทำอะไรเลยนอกจากสร้างและส่งคืนเคอร์เซอร์และ__exit__วิธีการที่คอมมิตหรือย้อนกลับ (ขึ้นอยู่กับว่ามีการโยนข้อยกเว้นหรือไม่) มันไม่ปิดการเชื่อมต่อ
  • เคอร์เซอร์ใน PyMySQL เป็นนามธรรมที่นำมาใช้ใน Python เท่านั้น ไม่มีแนวคิดที่เทียบเท่าใน MySQL 1
  • เคอร์เซอร์มี__enter__วิธีการที่ไม่ทำอะไรเลยและ__exit__วิธีที่ "ปิด" เคอร์เซอร์ (ซึ่งหมายถึงการลบการอ้างอิงของเคอร์เซอร์ไปยังการเชื่อมต่อหลักและทิ้งข้อมูลใด ๆ ที่เก็บไว้บนเคอร์เซอร์)
  • เคอร์เซอร์มีการอ้างอิงถึงการเชื่อมต่อที่สร้างขึ้น แต่การเชื่อมต่อไม่ได้อ้างอิงถึงเคอร์เซอร์ที่พวกเขาสร้างขึ้น
  • การเชื่อมต่อมี__del__วิธีการที่ปิดพวกเขา
  • ตามhttps://docs.python.org/3/reference/datamodel.html CPython (การใช้งาน Python เริ่มต้น) ใช้การนับการอ้างอิงและลบออบเจ็กต์โดยอัตโนมัติเมื่อจำนวนการอ้างอิงถึงศูนย์

เมื่อรวมสิ่งเหล่านี้เข้าด้วยกันเราจะเห็นว่ารหัสไร้เดียงสาเช่นนี้เป็นปัญหาในทางทฤษฎี :

# Problematic code, at least in theory!
import pymysql
with pymysql.connect() as cursor:
    cursor.execute('SELECT 1')

# ... happily carry on and do something unrelated

ปัญหาคือไม่มีอะไรปิดการเชื่อมต่อ อันที่จริงถ้าคุณวางโค้ดด้านบนลงใน Python shell จากนั้นเรียกใช้SHOW FULL PROCESSLISTที่ MySQL shell คุณจะสามารถเห็นการเชื่อมต่อที่ไม่ได้ใช้งานที่คุณสร้างขึ้น เนื่องจากจำนวนการเชื่อมต่อเริ่มต้นของ MySQL คือ151ซึ่งไม่มากคุณอาจเริ่มพบปัญหาในทางทฤษฎีหากคุณมีกระบวนการมากมายที่ทำให้การเชื่อมต่อเหล่านี้เปิดอยู่

อย่างไรก็ตามใน CPython มีการประหยัดที่ช่วยให้มั่นใจได้ว่าโค้ดดังตัวอย่างข้างต้นอาจไม่ทำให้คุณต้องทิ้งการเชื่อมต่อที่เปิดไว้มากมาย พระคุณการประหยัดนั้นคือทันทีที่cursorออกนอกขอบเขต (เช่นฟังก์ชันที่สร้างเสร็จสิ้นหรือcursorได้รับค่าอื่นที่กำหนดให้) จำนวนการอ้างอิงจะมีค่าเป็นศูนย์ซึ่งทำให้ถูกลบทิ้งจำนวนการอ้างอิงของการเชื่อมต่อ เป็นศูนย์ทำให้มี__del__การเรียกวิธีการเชื่อมต่อซึ่งบังคับปิดการเชื่อมต่อ หากคุณวางโค้ดข้างต้นลงในหลามเปลือกของคุณแล้วตอนนี้คุณสามารถจำลองนี้โดยการทำงานcursor = 'arbitrary value'; ทันทีที่คุณดำเนินการนี้การเชื่อมต่อที่คุณเปิดจะหายไปจากSHOW PROCESSLISTเอาต์พุต

อย่างไรก็ตามการพึ่งพาสิ่งนี้เป็นสิ่งที่ไม่ดีและในทางทฤษฎีอาจล้มเหลวในการใช้งาน Python นอกเหนือจาก CPython ตามทฤษฎีแล้ว Cleaner จะเป็นการ.close()เชื่อมต่ออย่างชัดเจน(เพื่อเพิ่มการเชื่อมต่อบนฐานข้อมูลโดยไม่ต้องรอให้ Python ทำลายวัตถุ) รหัสที่แข็งแกร่งกว่านี้มีลักษณะดังนี้:

import contextlib
import pymysql
with contextlib.closing(pymysql.connect()) as conn:
    with conn as cursor:
        cursor.execute('SELECT 1')

สิ่งนี้น่าเกลียด แต่ไม่ต้องพึ่งพา Python ที่ทำลายวัตถุของคุณเพื่อเพิ่มพื้นที่การเชื่อมต่อฐานข้อมูล (จำนวน จำกัด ) ของคุณ

สังเกตว่าการปิดเคอร์เซอร์หากคุณปิดการเชื่อมต่ออย่างโจ่งแจ้งเช่นนี้แล้วจะไม่มีจุดหมายโดยสิ้นเชิง

สุดท้ายเพื่อตอบคำถามรองที่นี่:

มีค่าใช้จ่ายจำนวนมากสำหรับการรับเคอร์เซอร์ใหม่หรือไม่ใช่เรื่องใหญ่?

ไม่การสร้างอินสแตนซ์เคอร์เซอร์จะไม่เข้าสู่ MySQL เลยและโดยพื้นฐานแล้วจะไม่ทำอะไรเลย

มีข้อได้เปรียบที่สำคัญในการค้นหาชุดธุรกรรมที่ไม่ต้องใช้การคอมมิชชันกลางเพื่อที่คุณจะได้ไม่ต้องรับเคอร์เซอร์ใหม่สำหรับแต่ละธุรกรรมหรือไม่?

นี่เป็นสถานการณ์และยากที่จะให้คำตอบทั่วไป ตามที่https://dev.mysql.com/doc/refman/en/optimizing-innodb-transaction-management.html ระบุว่า"แอปพลิเคชันอาจพบปัญหาด้านประสิทธิภาพหากทำงานหลายพันครั้งต่อวินาทีและปัญหาด้านประสิทธิภาพที่แตกต่างกันหาก มันทำงานทุกๆ 2-3 ชั่วโมงเท่านั้น" . คุณจ่ายค่าใช้จ่ายตามประสิทธิภาพสำหรับการคอมมิตทุกครั้ง แต่การปล่อยให้ธุรกรรมเปิดไว้นานขึ้นคุณจะเพิ่มโอกาสที่การเชื่อมต่ออื่น ๆ ต้องเสียเวลารอการล็อกเพิ่มความเสี่ยงต่อการชะงักงันและอาจเพิ่มค่าใช้จ่ายในการค้นหาบางรายการที่ดำเนินการโดยการเชื่อมต่ออื่น ๆ .


1 MySQL ไม่ได้สร้างมันเรียกเคอร์เซอร์แต่พวกเขาเท่านั้นที่มีอยู่ขั้นตอนที่เก็บไว้ภายใน; พวกมันแตกต่างอย่างสิ้นเชิงกับเคอร์เซอร์ PyMySQL และไม่เกี่ยวข้องกับที่นี่


5

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

conn = MySQLdb.connect("host","user","pass","database")
cursor = conn.cursor()
cursor.execute("somestuff")
results = cursor.fetchall()
..do stuff with results
cursor.execute("someotherstuff")
results2 = cursor.fetchall()
..do stuff with results2
cursor.close()

ประเด็นคือคุณสามารถจัดเก็บผลลัพธ์ของการดำเนินการของเคอร์เซอร์ในตัวแปรอื่นได้ซึ่งจะทำให้เคอร์เซอร์ของคุณว่างเพื่อทำการเรียกใช้งานครั้งที่สอง คุณพบปัญหาด้วยวิธีนี้ก็ต่อเมื่อคุณใช้ fetchone () และจำเป็นต้องทำการเรียกใช้เคอร์เซอร์ครั้งที่สองก่อนที่คุณจะวนซ้ำผลลัพธ์ทั้งหมดจากการสืบค้นแรก

มิฉะนั้นฉันจะบอกว่าแค่ปิดเคอร์เซอร์ของคุณทันทีที่คุณนำข้อมูลทั้งหมดออกจากพวกเขาเสร็จแล้ว ด้วยวิธีนี้คุณไม่ต้องกังวลกับการผูกปลายหลวมในภายหลังในรหัสของคุณ


ขอบคุณ - เมื่อพิจารณาว่าคุณต้องปิดเคอร์เซอร์เพื่อทำการอัปเดต / แทรกฉันเดาว่าวิธีง่ายๆอย่างหนึ่งในการอัปเดต / แทรกคือการได้รับเคอร์เซอร์หนึ่งตัวสำหรับแต่ละภูตปิดเคอร์เซอร์เพื่อยอมรับและรับเคอร์เซอร์ใหม่ทันที เพื่อให้คุณพร้อมในครั้งต่อไป ฟังดูสมเหตุสมผลไหม
jmilloy

1
เฮ้ไม่มีปัญหา ฉันไม่รู้จริงๆเกี่ยวกับการอัปเดต / แทรกโดยการปิดเคอร์เซอร์ของคุณ แต่การค้นหาอย่างรวดเร็วทางออนไลน์แสดงให้เห็นสิ่งนี้: conn = MySQLdb.connect (arguments_go_here) cursor = MySQLdb.cursor () cursor.execute (mysql_insert_statement_here) ลอง: conn กระทำ () ยกเว้น: conn.rollback () # เลิกทำการเปลี่ยนแปลงที่เกิดขึ้นหากเกิดข้อผิดพลาด ด้วยวิธีนี้ฐานข้อมูลจะยอมรับการเปลี่ยนแปลงและคุณไม่ต้องกังวลเกี่ยวกับเคอร์เซอร์เอง จากนั้นคุณสามารถเปิดเคอร์เซอร์ได้ 1 ครั้งตลอดเวลา ดูได้ที่นี่: tutorialspoint.com/python/python_database_access.htm
nct25

ใช่ถ้ามันใช้งานได้ฉันก็ผิดและมีเหตุผลอื่นที่ทำให้ฉันคิดว่าฉันต้องปิดเคอร์เซอร์เพื่อที่จะเชื่อมต่อ
jmilloy

ใช่ฉันไม่รู้ลิงค์ที่ฉันโพสต์ทำให้ฉันคิดว่าใช้ได้ ฉันเดาว่าการวิจัยเพิ่มเติมเล็กน้อยจะบอกคุณได้ว่ามันได้ผลจริงหรือไม่ แต่ฉันคิดว่าคุณน่าจะไปด้วยได้ หวังว่าฉันจะช่วยคุณได้!
nct25

เคอร์เซอร์ไม่ปลอดภัยต่อเธรดหากคุณใช้เคอร์เซอร์เดียวกันกับเธรดที่แตกต่างกันจำนวนมากและเคอร์เซอร์ทั้งหมดกำลังค้นหาจาก db fetchall () จะให้ข้อมูลแบบสุ่ม
ospider

-6

ฉันแนะนำให้ทำเช่น php และ mysql เริ่ม i ที่จุดเริ่มต้นของรหัสของคุณก่อนพิมพ์ข้อมูลแรก ดังนั้นหากคุณได้รับข้อผิดพลาดในการเชื่อมต่อคุณสามารถแสดง50xข้อความแสดงข้อผิดพลาด (อย่าจำว่าข้อผิดพลาดภายในคืออะไร) และเปิดไว้ตลอดเซสชันและปิดเมื่อคุณรู้ว่าคุณไม่ต้องการอีกต่อไป


ใน MySQLdb มีความแตกต่างระหว่างการเชื่อมต่อและเคอร์เซอร์ ฉันเชื่อมต่อหนึ่งครั้งต่อคำขอ (ตอนนี้) และสามารถตรวจพบข้อผิดพลาดในการเชื่อมต่อได้ตั้งแต่เนิ่นๆ แต่เคอร์เซอร์ล่ะ?
jmilloy

IMHO ไม่ใช่คำแนะนำที่ถูกต้อง มันขึ้นอยู่กับ หากรหัสของคุณจะเชื่อมต่อเป็นเวลานาน (เช่นใช้ข้อมูลบางส่วนจาก DB จากนั้นประมาณ 1-5-10 นาทีมันจะทำอะไรบางอย่างบนเซิร์ฟเวอร์และทำการเชื่อมต่อ) และเป็นแอปพลิเคชันเธรดหลายเธรดมันจะสร้างปัญหาในไม่ช้า (คุณ จะเกินการเชื่อมต่อสูงสุดที่อนุญาต)
Roman Podlinov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.