Python sqlite3 และการทำงานพร้อมกัน


87

ฉันมีโปรแกรม Python ที่ใช้โมดูล "เธรด" ทุกๆวินาทีโปรแกรมของฉันจะเริ่มเธรดใหม่เพื่อดึงข้อมูลบางส่วนจากเว็บและเก็บข้อมูลนี้ไว้ในฮาร์ดไดรฟ์ ฉันต้องการใช้ sqlite3 เพื่อเก็บผลลัพธ์เหล่านี้ แต่ไม่สามารถใช้งานได้ ปัญหาน่าจะเกี่ยวกับบรรทัดต่อไปนี้:

conn = sqlite3.connect("mydatabase.db")
  • ถ้าฉันใส่รหัสบรรทัดนี้ในแต่ละเธรดฉันจะได้รับ OperationalError ที่บอกฉันว่าไฟล์ฐานข้อมูลถูกล็อก ฉันเดาว่านี่หมายความว่าเธรดอื่นเปิด mydatabase.db ผ่านการเชื่อมต่อ sqlite3 และได้ล็อกไว้
  • ถ้าฉันใส่โค้ดบรรทัดนี้ในโปรแกรมหลักและส่งต่ออ็อบเจกต์การเชื่อมต่อ (conn) ไปยังแต่ละเธรดฉันจะได้รับ ProgrammingError โดยบอกว่าอ็อบเจ็กต์ SQLite ที่สร้างในเธรดสามารถใช้ได้ในเธรดเดียวกันเท่านั้น

ก่อนหน้านี้ฉันเก็บผลการค้นหาทั้งหมดไว้ในไฟล์ CSV และไม่มีปัญหาการล็อกไฟล์เหล่านี้ หวังว่าจะเป็นไปได้กับ sqlite ความคิดใด ๆ ?


5
ฉันต้องการทราบว่า Python เวอร์ชันล่าสุดมี sqlite3 เวอร์ชันใหม่กว่าซึ่งควรแก้ไขปัญหานี้
Ryan Fugger

@RyanFugger คุณรู้หรือไม่ว่ารุ่นแรกสุดที่รองรับนี้คืออะไร? ฉันใช้ 2.7
notbad.jpeg

@RyanFugger AFAIK ไม่มีเวอร์ชันที่สร้างไว้ล่วงหน้าซึ่งมี SQLite3 เวอร์ชันใหม่กว่าที่ได้รับการแก้ไขแล้ว คุณสามารถสร้างขึ้นมาเองได้
shezi

คำตอบ:


44

คุณสามารถใช้รูปแบบผู้บริโภค - ผู้ผลิต ตัวอย่างเช่นคุณสามารถสร้างคิวที่ใช้ร่วมกันระหว่างเธรด เธรดแรกที่ดึงข้อมูลจากเว็บจะสร้างข้อมูลนี้ในคิวที่ใช้ร่วมกัน เธรดอื่นที่เป็นเจ้าของการเชื่อมต่อฐานข้อมูล dequeues ข้อมูลจากคิวและส่งต่อไปยังฐานข้อมูล


8
FWIW: การอ้างสิทธิ์ sqlite เวอร์ชันที่ใหม่กว่าคุณสามารถแชร์การเชื่อมต่อและวัตถุข้ามเธรดได้ (ยกเว้นเคอร์เซอร์) แต่ฉันพบในทางปฏิบัติจริง
Richard Levasseur

นี่คือตัวอย่างของสิ่งที่ Evgeny Lazin กล่าวไว้ข้างต้น
dugres

4
การซ่อนฐานข้อมูลของคุณไว้หลังคิวที่ใช้ร่วมกันเป็นวิธีแก้ปัญหาที่ไม่ดีสำหรับคำถามนี้เนื่องจากโดยทั่วไปแล้ว SQL และ SQLite มีกลไกการล็อกในตัวอยู่แล้วซึ่งอาจได้รับการขัดเกลามากกว่าสิ่งใด ๆ ที่คุณสามารถสร้างเฉพาะกิจได้ด้วยตัวเอง
shezi

1
คุณต้องอ่านคำถามในขณะนั้นไม่มีกลไกการล็อคในตัว ฐานข้อมูลแบบฝังร่วมสมัยจำนวนมากขาดกลไกนี้เนื่องจากเหตุผลด้านประสิทธิภาพ (ตัวอย่างเช่น: LevelDB)
Evgeny Lazin

180

ขัดกับความเชื่อที่นิยมรุ่นใหม่ของ sqlite3 ทำเข้าถึงการสนับสนุนจากหลายหัวข้อ

สามารถเปิดใช้งานผ่านอาร์กิวเมนต์คำหลักที่เป็นทางเลือกcheck_same_thread:

sqlite.connect(":memory:", check_same_thread=False)

4
ฉันพบข้อยกเว้นที่คาดเดาไม่ได้และแม้แต่ Python ก็ขัดข้องด้วยตัวเลือกนี้ (Python 2.7 บน Windows 32)
reclosedev

4
ตามเอกสารในโหมดมัลติเธรดจะไม่สามารถใช้การเชื่อมต่อฐานข้อมูลเดียวในหลายเธรดได้ นอกจากนี้ยังมีโหมดซีเรียล
ไลซ์

1
ไม่เป็นไรเพิ่งพบ: http://sqlite.org/compile.html#threadsafe
Medeiros

1
@FrEaKmAn ขออภัยมันนานมาแล้วไม่ใช่: memory: database หลังจากนั้นฉันไม่ได้แชร์การเชื่อมต่อ sqlite ในหลายเธรด
reclosedev

2
@FrEaKmAn ฉันเจอสิ่งนี้ด้วยการประมวลผลหลักของ python ในการเข้าถึงแบบมัลติเธรด พฤติกรรมไม่สามารถคาดเดาได้และไม่มีการบันทึกข้อยกเว้น ถ้าจำไม่ผิดนี่เป็นจริงทั้งอ่านและเขียน นี่คือสิ่งหนึ่งที่ฉันเคยเห็นงูหลามผิดพลาดจริง ๆ จนถึงตอนนี้: D ฉันไม่ได้ลองใช้ sqlite ที่คอมไพล์ในโหมด threadsafe แต่ในเวลานั้นฉันไม่มีเสรีภาพในการคอมไพล์ sqlite เริ่มต้นของระบบใหม่ ฉันลงเอยด้วยการทำบางอย่างที่คล้ายกับสิ่งที่ Eric แนะนำและปิดใช้งานความเข้ากันได้ของเธรด
verboze

17

สิ่งต่อไปนี้พบในmail.python.org.pipermail.1239789

ฉันพบวิธีแก้ปัญหาแล้ว ฉันไม่รู้ว่าทำไมเอกสาร python ไม่มีคำเดียวเกี่ยวกับตัวเลือกนี้ ดังนั้นเราจึงต้องเพิ่มอาร์กิวเมนต์คำหลักใหม่ในฟังก์ชันการเชื่อมต่อและเราจะสามารถสร้างเคอร์เซอร์จากมันในเธรดอื่นได้ ดังนั้นใช้:

sqlite.connect(":memory:", check_same_thread = False)

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


(ด้วย GIL ไม่มีทางเข้าถึงฐานข้อมูลแบบมัลติเธรดที่แท้จริงได้มากนัก)
Erik Aronesty

คำเตือน : เอกสารหลามได้นี้จะพูดเกี่ยวกับcheck_same_threadตัวเลือก "เมื่อใช้หลายหัวข้อที่มีการเชื่อมต่อกันเขียนการดำเนินงานควรจะต่อเนื่องกันโดยผู้ใช้สามารถหลีกเลี่ยงความเสียหายของข้อมูล" ใช่คุณสามารถใช้ SQLite กับเธรดหลายเธรดได้ตราบเท่าที่โค้ดของคุณทำให้แน่ใจว่าเธรดเดียวเท่านั้นที่สามารถเขียนไปยังฐานข้อมูลได้ตลอดเวลา หากไม่เป็นเช่นนั้นคุณอาจทำให้ฐานข้อมูลของคุณเสียหาย
Ajedi32

14

เปลี่ยนเป็นการประมวลผลหลายขั้นตอน จะดีกว่ามากสเกลได้ดีสามารถไปไกลกว่าการใช้หลายคอร์โดยใช้ซีพียูหลายตัวและอินเทอร์เฟซเหมือนกับการใช้โมดูลเธรด python

หรือเป็นอาลีปัญหาเพียงแค่ใช้กลไกด้ายร่วมกัน SQLAlchemy ของ มันจะจัดการทุกอย่างให้คุณโดยอัตโนมัติและมีคุณสมบัติพิเศษมากมายเพียงแค่อ้างถึงบางส่วน:

  1. SQLAlchemy ประกอบด้วยภาษาถิ่นสำหรับ SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase และ Informix ไอบีเอ็มยังได้เปิดตัวไดรเวอร์ DB2 ดังนั้นคุณไม่จำเป็นต้องเขียนแอปพลิเคชันของคุณใหม่หากคุณตัดสินใจที่จะย้ายออกจาก SQLite
  2. ระบบ Unit Of Work ซึ่งเป็นส่วนกลางของ Object Relational Mapper (ORM) ของ SQLAlchemy จะจัดระเบียบการสร้าง / แทรก / อัปเดต / ลบที่รอดำเนินการลงในคิวและล้างข้อมูลทั้งหมดในชุดเดียว เพื่อให้บรรลุเป้าหมายนี้จะดำเนินการ "เรียงลำดับการอ้างอิง" แบบทอพอโลยีของรายการที่แก้ไขทั้งหมดในคิวเพื่อให้เป็นไปตามข้อ จำกัด ของคีย์ต่างประเทศและจัดกลุ่มคำสั่งที่ซ้ำซ้อนเข้าด้วยกัน สิ่งนี้ก่อให้เกิดประสิทธิภาพสูงสุดและความปลอดภัยในการทำธุรกรรมและลดโอกาสที่จะเกิดการชะงักงัน

11

คุณไม่ควรใช้เธรดเลยสำหรับสิ่งนี้ นี่เป็นงานที่ไม่สำคัญสำหรับนักบิดและนั่นอาจจะนำคุณไปไกลกว่านั้น

ใช้เธรดเดียวเท่านั้นและทำให้การร้องขอเสร็จสมบูรณ์ทริกเกอร์เหตุการณ์ที่จะเขียน

บิดจะดูแลการตั้งเวลาการโทรกลับ ฯลฯ ... สำหรับคุณ มันจะส่งผลลัพธ์ทั้งหมดให้คุณเป็นสตริงหรือคุณสามารถเรียกใช้ผ่านโปรเซสเซอร์สตรีม (ฉันมีtwitter APIและfriendfeed APIที่ทั้งสองปิดเหตุการณ์ไปยังผู้โทรเนื่องจากผลลัพธ์ยังคงถูกดาวน์โหลด)

ขึ้นอยู่กับสิ่งที่คุณกำลังทำกับข้อมูลของคุณคุณสามารถถ่ายโอนผลลัพธ์ทั้งหมดลงใน sqlite เมื่อเสร็จสมบูรณ์ปรุงและถ่ายโอนข้อมูลหรือปรุงอาหารในขณะที่อ่านและถ่ายโอนข้อมูลในตอนท้าย

ฉันมีแอปพลิเคชั่นง่ายๆที่ทำสิ่งที่ใกล้เคียงกับสิ่งที่คุณต้องการบน github ฉันเรียกมันว่าpfetch (การดึงข้อมูลแบบขนาน) มันคว้าหน้าต่างๆตามกำหนดเวลาสตรีมผลลัพธ์ไปยังไฟล์และเรียกใช้สคริปต์เมื่อเสร็จสมบูรณ์ นอกจากนี้ยังมีสิ่งที่น่าสนใจเช่น GET ที่มีเงื่อนไข แต่ก็ยังอาจเป็นฐานที่ดีสำหรับสิ่งที่คุณกำลังทำ


7

หรือถ้าขี้เกียจเหมือนผมก็ใช้SQLAlchemyก็ได้ มันจะจัดการเธรดให้คุณ ( โดยใช้เธรดโลคัลและการเชื่อมต่อร่วมกัน ) และวิธีที่มันกำหนดได้

สำหรับโบนัสเพิ่มเติมหาก / เมื่อคุณตระหนัก / ตัดสินใจว่าการใช้ Sqlite สำหรับแอปพลิเคชันใด ๆ ที่ทำงานพร้อมกันจะเป็นหายนะคุณจะไม่ต้องเปลี่ยนรหัสเพื่อใช้ MySQL หรือ Postgres หรือสิ่งอื่นใด คุณสามารถสลับไปมาได้


1
เหตุใดจึงไม่ระบุเวอร์ชัน Python ที่ใดก็ได้บนเว็บไซต์ทางการ
ชื่อที่ใช้แสดง

3

คุณต้องใช้ทุกครั้งsession.close()หลังการทำธุรกรรมกับฐานข้อมูลเพื่อที่จะใช้เคอร์เซอร์เดียวกันในเธรดเดียวกันโดยไม่ใช้เคอร์เซอร์เดียวกันในเธรดหลายเธรดซึ่งทำให้เกิดข้อผิดพลาดนี้



0

ฉันชอบคำตอบของ Evgeny - โดยทั่วไปคิวเป็นวิธีที่ดีที่สุดในการใช้การสื่อสารระหว่างเธรด เพื่อความสมบูรณ์นี่คือตัวเลือกอื่น ๆ :

  • ปิดการเชื่อมต่อ DB เมื่อเธรดที่สร้างเสร็จสิ้นใช้งานได้แล้ว สิ่งนี้จะแก้ไขไฟล์OperationalErrorแต่การเปิดและปิดการเชื่อมต่อเช่นนี้โดยทั่วไปจะเป็น No-No เนื่องจากค่าใช้จ่ายด้านประสิทธิภาพ
  • อย่าใช้เธรดย่อย หากงานหนึ่งครั้งต่อวินาทีมีน้ำหนักเบาพอสมควรคุณสามารถหลีกเลี่ยงการดึงและจัดเก็บจากนั้นจึงเข้าสู่ช่วงเวลาที่เหมาะสม นี่เป็นสิ่งที่ไม่พึงปรารถนาเนื่องจากการดำเนินการดึงและจัดเก็บอาจใช้เวลา> 1 วินาทีและคุณจะสูญเสียประโยชน์จากทรัพยากรมัลติเพล็กซ์ที่คุณมีด้วยวิธีการแบบมัลติเธรด

0

คุณต้องออกแบบการทำงานพร้อมกันสำหรับโปรแกรมของคุณ SQLite มีข้อ จำกัด ที่ชัดเจนและคุณต้องปฏิบัติตามโปรดดูคำถามที่พบบ่อย (เช่นคำถามต่อไปนี้)


0

Scrapyดูเหมือนจะเป็นคำตอบสำหรับคำถามของฉัน โฮมเพจอธิบายถึงงานที่แน่นอนของฉัน (แม้ว่าฉันจะไม่แน่ใจว่ารหัสนั้นเสถียรแค่ไหน)


0

ฉันจะดูโมดูล y_serial Python สำหรับการคงอยู่ของข้อมูล: http://yserial.sourceforge.net

ซึ่งจัดการปัญหาการชะงักงันรอบฐานข้อมูล SQLite เดียว หากความต้องการในการทำงานพร้อมกันมีปริมาณมากก็สามารถตั้งค่าคลาส Farm ของฐานข้อมูลจำนวนมากเพื่อกระจายภาระในช่วงเวลาสุ่ม

หวังว่านี่จะช่วยโครงการของคุณ ... มันควรจะง่ายพอที่จะใช้งานได้ใน 10 นาที


0

ฉันไม่พบเกณฑ์มาตรฐานในคำตอบใด ๆ ข้างต้นดังนั้นฉันจึงเขียนแบบทดสอบเพื่อเปรียบเทียบทุกอย่าง

ฉันลอง 3 วิธีแล้ว

  1. การอ่านและเขียนตามลำดับจากฐานข้อมูล SQLite
  2. ใช้ ThreadPoolExecutor เพื่ออ่าน / เขียน
  3. ใช้ ProcessPoolExecutor เพื่ออ่าน / เขียน

ผลลัพธ์และสิ่งที่ได้รับจากเกณฑ์มาตรฐานมีดังนี้

  1. การอ่านตามลำดับ / การเขียนตามลำดับจะทำงานได้ดีที่สุด
  2. หากคุณต้องประมวลผลแบบขนานให้ใช้ ProcessPoolExecutor เพื่ออ่านแบบขนาน
  3. อย่าทำการเขียนใด ๆ โดยใช้ ThreadPoolExecutor หรือใช้ ProcessPoolExecutor เนื่องจากคุณจะพบข้อผิดพลาดที่ฐานข้อมูลถูกล็อกและคุณจะต้องลองแทรกชิ้นส่วนอีกครั้ง

คุณสามารถค้นหารหัสและโซลูชันที่สมบูรณ์สำหรับเกณฑ์มาตรฐานได้ในคำตอบ SO ของฉันที่นี่หวังว่าจะช่วยได้!


-1

สาเหตุส่วนใหญ่ที่คุณได้รับข้อผิดพลาดเกี่ยวกับฐานข้อมูลที่ถูกล็อกคือคุณต้องออก

conn.commit()

หลังจากเสร็จสิ้นการดำเนินการฐานข้อมูล หากคุณไม่ทำเช่นนั้นฐานข้อมูลของคุณจะถูกล็อกการเขียนและเป็นเช่นนั้น เธรดอื่น ๆ ที่รอการเขียนจะหมดเวลาหลังจากช่วงเวลาหนึ่ง (ค่าเริ่มต้นตั้งไว้ที่ 5 วินาทีโปรดดูhttp://docs.python.org/2/library/sqlite3.html#sqlite3.connectสำหรับรายละเอียดเกี่ยวกับเรื่องนั้น) .

ตัวอย่างของการแทรกที่ถูกต้องและพร้อมกันจะเป็นดังนี้:

import threading, sqlite3
class InsertionThread(threading.Thread):

    def __init__(self, number):
        super(InsertionThread, self).__init__()
        self.number = number

    def run(self):
        conn = sqlite3.connect('yourdb.db', timeout=5)
        conn.execute('CREATE TABLE IF NOT EXISTS threadcount (threadnum, count);')
        conn.commit()

        for i in range(1000):
            conn.execute("INSERT INTO threadcount VALUES (?, ?);", (self.number, i))
            conn.commit()

# create as many of these as you wish
# but be careful to set the timeout value appropriately: thread switching in
# python takes some time
for i in range(2):
    t = InsertionThread(i)
    t.start()

หากคุณชอบ SQLite หรือมีเครื่องมืออื่น ๆ ที่ทำงานกับฐานข้อมูล SQLite หรือต้องการแทนที่ไฟล์ CSV ด้วยไฟล์ SQLite db หรือต้องทำสิ่งที่หายากเช่น IPC ระหว่างแพลตฟอร์ม SQLite เป็นเครื่องมือที่ยอดเยี่ยมและเหมาะสมกับวัตถุประสงค์ อย่าปล่อยให้ตัวเองถูกกดดันให้ใช้วิธีแก้ปัญหาอื่นหากรู้สึกไม่ถูกต้อง!

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