การเข้าถึงพร้อมกันของ SQLite


177

SQLite3 จัดการการเข้าถึงพร้อมกันอย่างปลอดภัยโดยหลายกระบวนการอ่าน / เขียนจากฐานข้อมูลเดียวกันหรือไม่ มีข้อยกเว้นสำหรับแพลตฟอร์มใดบ้าง


3
ฉันลืมที่จะพูดถึงgoall รางวัล : คำตอบส่วนใหญ่บอกว่ามัน ok: "SQLite เร็วพอ", "SQLite จัดการพร้อมกันดี" ฯลฯ แต่ imho อย่าตอบในรายละเอียด / ไม่อธิบายอย่างชัดเจนว่าเกิดอะไรขึ้นถ้าสองการเขียน จะมาถึงในเวลาเดียวกัน (ในทางทฤษฎีกรณีที่หายากมาก) 1) มันจะทำให้เกิดข้อผิดพลาดและขัดจังหวะโปรแกรมหรือไม่ หรือ 2) การเขียนครั้งที่สองจะรอจนกว่าการเขียนครั้งแรกจะเสร็จสิ้นหรือไม่ หรือ 3) หนึ่งในการดำเนินการเขียนจะถูกยกเลิก (การสูญเสียข้อมูล!) หรือไม่? 4) มีอะไรอีกไหม? การรู้ข้อ จำกัด ของการเขียนพร้อมกันอาจมีประโยชน์ในหลาย ๆ สถานการณ์
Basj

7
@Basj ในระยะสั้น 2) มันจะรอและลองใหม่หลายครั้ง (กำหนดค่าได้), 1) ทริกเกอร์ข้อผิดพลาด, SQLITE_BUSY.3) คุณสามารถลงทะเบียนการติดต่อกลับเพื่อจัดการข้อผิดพลาด SQLITE_BUSY
obgnaw

คำตอบ:


112

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

สำหรับแอพพลิเคชันเดสก์ท็อป / แล็ปท็อป / แท็บเล็ต / โทรศัพท์ส่วนใหญ่ SQLite นั้นเร็วพอเนื่องจากไม่มีการทำงานพร้อมกัน (Firefox ใช้ SQLite อย่างกว้างขวางสำหรับบุ๊กมาร์กประวัติ ฯลฯ )

สำหรับแอปพลิเคชั่นเซิร์ฟเวอร์บางคนกล่าวเมื่อไม่นานมานี้ว่ามีการดูเพจน้อยกว่า 100K ต่อวันสามารถจัดการได้อย่างสมบูรณ์แบบโดยฐานข้อมูล SQLite ในสถานการณ์ทั่วไป (เช่นบล็อกฟอรัม) และฉันยังไม่เห็นหลักฐานใด ๆ ในความเป็นจริงด้วยดิสก์และตัวประมวลผลที่ทันสมัย ​​95% ของเว็บไซต์และบริการเว็บจะทำงานได้ดีกับ SQLite

ถ้าคุณอยากอ่านได้อย่างรวดเร็วเข้าถึง / เขียนใช้ฐานข้อมูล SQLite ในหน่วยความจำ RAM มีขนาดของคำสั่งเร็วกว่าดิสก์หลายเท่า


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

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

1
คุณจะจัดการการเข้าถึงฐานข้อมูล sqlite ในหน่วยความจำพร้อมกันได้อย่างไร
P-Gn

42

ใช่ SQLite จัดการกับการเกิดพร้อมกันได้ดี แต่มันไม่ได้ดีที่สุดจากมุมมองประสิทธิภาพ จากสิ่งที่ฉันสามารถบอกได้ไม่มีข้อยกเว้นใด ๆ รายละเอียดอยู่บนเว็บไซต์ของ SQLite: https://www.sqlite.org/lockingv3.html

คำสั่งนี้มีความน่าสนใจ: "โมดูลเพจเจอร์ทำให้แน่ใจว่าการเปลี่ยนแปลงเกิดขึ้นทั้งหมดในครั้งเดียวซึ่งการเปลี่ยนแปลงทั้งหมดเกิดขึ้นหรือไม่มีสิ่งใดเกิดขึ้นกระบวนการนั้นสองกระบวนการขึ้นไปไม่พยายามเข้าถึงฐานข้อมูลด้วยวิธีที่เข้ากันไม่ได้ในเวลาเดียวกัน"


2
นี่คือความคิดเห็นบางส่วนเกี่ยวกับปัญหาในแพลตฟอร์มที่แตกต่างกันได้แก่ ระบบไฟล์ NFS และ Windows (แม้ว่าอาจเกี่ยวข้องกับ Windows รุ่นเก่าเท่านั้น)
Nate

1
เป็นไปได้ไหมที่จะโหลดฐานข้อมูล SQLite3 ใน RAM เพื่อใช้กับผู้ใช้ทั้งหมดใน PHP? ฉันคาดเดาไม่ได้ว่ามันเป็นกระบวนการ
Anon343224user

1
@foxyfennec .. จุดเริ่มต้นแม้ว่า SQLite อาจไม่ใช่ db ที่ดีที่สุดสำหรับกรณีการใช้งานนี้ sqlite.org/inmemorydb.html
kingPuppy

37

ใช่แล้ว. ลองคิดดูว่าทำไม

SQLite เป็นธุรกรรม

การเปลี่ยนแปลงทั้งหมดภายในธุรกรรมเดียวใน SQLite อาจเกิดขึ้นอย่างสมบูรณ์หรือไม่เลย

สนับสนุนกรดดังกล่าวเช่นเดียวกับการเกิดขึ้นพร้อมกันในการอ่าน / เขียนไว้ใน 2 วิธี - โดยใช้สิ่งที่เรียกว่าบันทึก (ช่วยให้เรียกว่า“ วิธีการแบบเก่า ”) หรือการเข้าสู่ระบบเขียนล่วงหน้า (ช่วยให้เรียกว่า“ วิธีการใหม่ ”)

การจดบันทึก (วิธีเก่า)

ในโหมดนี้ SQLite ใช้ระดับฐานข้อมูล การล็อค นี่คือจุดสำคัญที่จะเข้าใจ

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

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

นี่คือเหตุผลที่นี่พวกเขากำลังจะบอกว่าการดำเนินการ SQLite serializableการทำธุรกรรม

ปัญหา

เนื่องจากมันต้องการล็อคฐานข้อมูลทั้งหมดทุกครั้งและทุกคนรอกระบวนการจัดการการเขียนภาวะพร้อมกันและการอ่าน / อ่านพร้อมกันนั้นมีประสิทธิภาพต่ำ

rollbacks / ขัดข้อง

ก่อนที่จะเขียนบางสิ่งลงในไฟล์ฐานข้อมูล SQLite ก่อนอื่นจะบันทึก chunk ที่จะเปลี่ยนในไฟล์ชั่วคราว หากมีปัญหาเกิดขึ้นในระหว่างการเขียนลงในไฟล์ฐานข้อมูลมันจะรับไฟล์ชั่วคราวนี้และกลับการเปลี่ยนแปลงจากมัน

การบันทึกการเขียนล่วงหน้าหรือ WAL (วิธีใหม่)

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

ดังนั้นผู้อ่านจะไม่แข่งขันกับนักเขียนและประสิทธิภาพจะดีกว่าเมื่อเทียบกับ Old Way

คำเตือน

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

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


34

ดูเหมือนว่าไม่มีใครพูดถึงโหมด WAL (Write Ahead Log) ตรวจสอบให้แน่ใจว่าธุรกรรมต่าง ๆ ได้รับการจัดการอย่างถูกต้องและเมื่อตั้งโหมด WAL ไว้ไม่จำเป็นต้องเก็บฐานข้อมูลไว้ในขณะที่ผู้คนกำลังอ่านสิ่งต่าง ๆ ในขณะที่มีการอัพเดทเกิดขึ้น

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


น่าสนใจ แต่ใช้ได้กับเครื่องเดียวเท่านั้นไม่ใช่ในสถานการณ์ที่ฐานข้อมูลเข้าถึงเครือข่าย onver
Bobík

เป็นมูลค่าการกล่าวถึงที่หมดเวลาเริ่มต้นสำหรับนักเขียนที่จะรอคือ 5 วินาทีและหลังจากdatabase is lockedข้อผิดพลาดที่จะเพิ่มขึ้นโดยนักเขียน
mirhossein

16

ในปี 2019 มีตัวเลือกการเขียนพร้อมกันใหม่สองตัวที่ยังไม่ออก แต่มีให้ในสาขาแยก

"PRAGMA journal_mode = wal2"

ข้อดีของโหมดเจอร์นัลนี้ในโหมด "wal" ปกติคือผู้เขียนอาจทำการเขียนต่อไปยังไฟล์ wal หนึ่งไฟล์ในขณะที่โหมดอื่นถูกตรวจสอบ

BEGIN CONCURRENT - ลิงค์ไปยังเอกสารรายละเอียด

การปรับปรุง BEGIN CONCURRENT ช่วยให้นักเขียนหลายคนสามารถประมวลผลธุรกรรมการเขียนพร้อมกันได้หากฐานข้อมูลอยู่ในโหมด "wal" หรือ "wal2" แม้ว่าระบบจะยังคงซีเรียลคำสั่ง COMMIT

เมื่อการเขียนธุรกรรมถูกเปิดด้วย "BEGIN CONCURRENT" การล็อกฐานข้อมูลจะถูกเลื่อนออกไปจนกว่าจะมีการเรียกใช้ COMMIT ซึ่งหมายความว่าจำนวนธุรกรรมที่เริ่มต้นด้วย BEGIN CONCURRENT อาจดำเนินการพร้อมกัน ระบบใช้การล็อกระดับหน้าในแง่ดีเพื่อป้องกันการทำธุรกรรมที่เกิดขึ้นพร้อมกันไม่ให้เกิดความขัดแย้ง

ร่วมกันพวกเขามีอยู่ในเริ่มต้นพร้อมกัน-wal2หรือในแต่ละเองแยกต่างหากสาขา


1
เรามีความคิดใด ๆ เมื่อฟีเจอร์เหล่านั้นเข้ามาในเวอร์ชั่นวางจำหน่ายหรือไม่? พวกเขามีประโยชน์กับฉันจริงๆ
Peter Moore

2
ไม่มีความเห็น. คุณสามารถสร้างได้ง่ายจากกิ่งไม้ สำหรับ. NET ฉันมีห้องสมุดที่มีอินเตอร์เฟสระดับต่ำ & WAL2 + เริ่มพร้อมกัน + FTS5: github.com/Spreads/Spreads.SQLite
VB

โอ้ขอบคุณแน่นอน ฉันสงสัยเกี่ยวกับความมั่นคงมากขึ้น SQLite เป็นอันดับต้น ๆ ของการวางจำหน่าย แต่ฉันไม่รู้ว่าการใช้สาขาในรหัสการผลิตนั้นมีความเสี่ยงแค่ไหน
Peter Moore

2
ดูกระทู้นี้github.com/Expensify/Bedrock/issues/65และ Bedrock โดยทั่วไป พวกเขาใช้ในการผลิตและผลักดันbegin concurrentสิ่งนั้น
VB

13

SQLite มีล็อกผู้อ่าน - ผู้เขียนในระดับฐานข้อมูล การเชื่อมต่อที่หลากหลาย (อาจเป็นเจ้าของกระบวนการที่แตกต่างกัน) สามารถอ่านข้อมูลจากฐานข้อมูลเดียวกันในเวลาเดียวกัน แต่มีเพียงคนเดียวเท่านั้นที่สามารถเขียนไปยังฐานข้อมูล

SQLite รองรับจำนวนผู้อ่านพร้อมกันไม่ จำกัด แต่จะอนุญาตให้ผู้เขียนหนึ่งคนในเวลาใดก็ได้ สำหรับหลาย ๆ สถานการณ์นี่ไม่ใช่ปัญหา คิวนักเขียน แต่ละแอปพลิเคชันทำงานกับฐานข้อมูลได้อย่างรวดเร็วและเดินหน้าต่อไปและไม่มีการล็อคเป็นเวลานานกว่าสองสามมิลลิวินาที แต่มีบางแอพพลิเคชั่นที่ต้องการการทำงานพร้อมกันมากกว่าเดิมและแอพพลิเคชั่นเหล่านั้นอาจต้องหาทางออกที่แตกต่างกัน - การใช้ที่เหมาะสมสำหรับ SQLite @ SQLite.org

ล็อคผู้อ่าน - ผู้เขียนเปิดใช้งานการประมวลผลธุรกรรมที่เป็นอิสระและถูกนำไปใช้โดยใช้การล็อกแบบเอกสิทธิ์

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

รายละเอียดการใช้งานสำหรับกรณีของการเขียนพร้อมกัน

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

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

หลังจากสถานะ UNLOCKED สถานะถัดไปคือสถานะ SHARED เพื่อให้สามารถอ่าน (ไม่เขียน) ข้อมูลจากฐานข้อมูลการเชื่อมต่อจะต้องเข้าสู่สถานะ SHARED ก่อนโดยรับล็อค SHARED การเชื่อมต่อที่หลากหลายสามารถรับและบำรุงรักษาการล็อค SHARED ในเวลาเดียวกันดังนั้นการเชื่อมต่อหลายรายการสามารถอ่านข้อมูลจากฐานข้อมูลเดียวกันในเวลาเดียวกัน แต่ตราบใดที่การล็อค SHARED เพียงอันเดียวยังคงไม่ได้เผยแพร่การเชื่อมต่อจะไม่สามารถเขียนไปยังฐานข้อมูลได้สำเร็จ

หากการเชื่อมต่อต้องการที่จะเขียนไปยังฐานข้อมูลนั้นจะต้องได้รับล็อค RESERVED ก่อน

ล็อค RESERVED เดียวเท่านั้นที่สามารถใช้งานได้ในคราวเดียวแม้ว่าการล็อค SHARED หลายครั้งสามารถอยู่ร่วมกันได้ด้วยการล็อค RESERVED เดียว RESERVED นั้นแตกต่างจาก PENDING ในล็อค SHARED ใหม่ที่สามารถรับได้ในขณะที่มีล็อค RESERVED - การล็อคไฟล์และการทำงานพร้อมกันใน SQLite เวอร์ชัน 3 @ SQLite.org

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

การล็อคที่รออยู่หมายความว่ากระบวนการที่ถือล็อคต้องการเขียนไปยังฐานข้อมูลโดยเร็วที่สุดและกำลังรอการล็อค SHARED ปัจจุบันทั้งหมดเพื่อล้างเพื่อให้สามารถล็อค EXCLUSIVE ได้ ไม่อนุญาตให้ใช้การล็อก SHARED ใหม่กับฐานข้อมูลหากการล็อก PENDING ทำงานอยู่แม้ว่าการล็อก SHARED ที่มีอยู่จะได้รับอนุญาตให้ดำเนินการต่อได้

จำเป็นต้องใช้การล็อค EXCLUSIVE เพื่อเขียนไปยังไฟล์ฐานข้อมูล อนุญาตให้ล็อกเอกสิทธิ์ EXCLUSIVE เดียวเท่านั้นในไฟล์และห้ามมิให้ล็อคอื่นใดที่อยู่ร่วมกับ EXCLUSIVE lock เพื่อให้เกิดการทำงานพร้อมกันสูงสุด SQLite จะทำงานเพื่อลดระยะเวลาในการล็อค EXCLUSIVE ให้น้อยที่สุด - การล็อคไฟล์และการทำงานพร้อมกันใน SQLite เวอร์ชัน 3 @ SQLite.org

ดังนั้นคุณอาจบอกว่า SQLite จัดการการเข้าถึงที่เกิดขึ้นพร้อมกันได้อย่างปลอดภัยโดยกระบวนการหลายกระบวนการที่เขียนไปยังฐานข้อมูลเดียวกันเพราะมันไม่รองรับ! คุณจะได้รับSQLITE_BUSYหรือSQLITE_LOCKEDสำหรับนักเขียนคนที่สองเมื่อพบข้อ จำกัด การลองใหม่


ขอบคุณ. ตัวอย่างของโค้ดที่มีนักเขียน 2 คนนั้นยอดเยี่ยมมากที่จะเข้าใจว่ามันทำงานอย่างไร
Basj

1
@Basj ในระยะสั้น sqlite มีล็อคการอ่าน - เขียนบนไฟล์ฐานข้อมูลมันเหมือนกับการเขียนไฟล์พร้อมกัน และด้วย WAL ยังคงไม่สามารถเขียนพร้อมกันได้ แต่ WAL สามารถเพิ่มความเร็วในการเขียนและการอ่านและการเขียนสามารถเกิดขึ้นพร้อมกันและ WAL จะแนะนำการล็อกใหม่เช่น WAL_READ_LOCK, WAL_WRITE_LOCK
obgnaw

2
คุณสามารถใช้คิวและมีหลายเธรดฟีดคิวและเพียงหนึ่งเธรดที่เขียนไปยังฐานข้อมูลโดยใช้คำสั่ง SQL ในคิว บางอย่างเช่นนี้
Gabriel

7

เธรดนี้เก่า แต่ฉันคิดว่ามันจะเป็นการดีที่จะแบ่งปันผลการทดสอบของฉันใน sqlite: ฉันรันโปรแกรมอินสแตนซ์ของ python 2 โปรแกรม (กระบวนการที่แตกต่างกันในโปรแกรมเดียวกัน) ดำเนินการคำสั่ง SELECT และ UPDATE sql คำสั่ง 10 วินาทีในการล็อคและผลลัพธ์ก็น่าผิดหวัง ทุกตัวอย่างทำใน 10000 step loop

  • เชื่อมต่อกับ db ด้วยการล็อคพิเศษ
  • เลือกหนึ่งแถวเพื่ออ่านตัวนับ
  • อัปเดตแถวด้วยค่าใหม่เท่ากับการเพิ่มตัวนับโดย 1
  • ปิดการเชื่อมต่อกับ db

แม้ว่า sqlite จะได้รับการล็อคแบบเอกสิทธิ์เฉพาะบุคคลในการทำธุรกรรมจำนวนรอบของการดำเนินการจริง ๆ นั้นไม่เท่ากับ 20,000 แต่น้อยกว่า (จำนวนการวนซ้ำทั้งหมดบนตัวนับเดียวนับสำหรับกระบวนการทั้งสอง) โปรแกรม Python แทบจะไม่มีข้อยกเว้นใด ๆ เลย (เพียงครั้งเดียวในระหว่างการเลือกสำหรับการประมวลผล 20 ครั้ง) การแก้ไข sqlite ในขณะทำการทดสอบคือ 3.6.20 และ python v3.3 CentOS 6.5 ในความคิดของฉันมันจะดีกว่าที่จะหาผลิตภัณฑ์ที่น่าเชื่อถือมากขึ้นสำหรับงานประเภทนี้หรือ จำกัด การเขียนไปยัง sqlite เพื่อกระบวนการที่ไม่ซ้ำกัน / ด้าย


5
ดูเหมือนว่าคุณจะต้องพูดคำวิเศษเพื่อที่จะได้ล็อคในไพ ธ อนดังที่กล่าวไว้ที่นี่: stackoverflow.com/a/12848059/1048959 สิ่งนี้แม้จะมีข้อเท็จจริงที่ว่าเอกสารหลาม sqlite ทำให้คุณเชื่อว่าwith conเพียงพอ
Dan Stahlke
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.