วิธีจัดการกับการเชื่อมต่อฐานข้อมูลในโมดูลไลบรารี Python


23

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

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


1
จัดเตรียมopenConnฟังก์ชั่นและทำให้ผู้ใช้ส่งผ่านไปยังแต่ละฟังก์ชั่นที่พวกเขาเรียกวิธีที่พวกเขาสามารถกำหนดขอบเขตการเชื่อมต่อในwithคำสั่งหรืออะไรก็ตาม
Daniel Gratzer

1
ฉันเห็นด้วยกับ jozfeg พิจารณาการสร้างคลาสที่เปิดการเชื่อมต่อฐานข้อมูลภายในตัวสร้างและปิดการเชื่อมต่อที่ทางออก
Nick Burns

คำตอบ:


31

ขึ้นอยู่กับห้องสมุดที่คุณใช้ บางคนอาจปิดการเชื่อมต่อด้วยตนเอง (หมายเหตุ: ฉันตรวจสอบบิวอิน builtin sqlite3 และไม่ได้) Python จะเรียก destructor เมื่อวัตถุออกนอกขอบเขตและไลบรารีเหล่านี้อาจใช้ตัวทำลายระบบที่ปิดการเชื่อมต่ออย่างงดงาม

อย่างไรก็ตามนั่นอาจไม่ใช่กรณี! ฉันอยากจะแนะนำตามที่คนอื่นมีในความคิดเห็นเพื่อห่อไว้ในวัตถุ

class MyDB(object):

    def __init__(self):
        self._db_connection = db_module.connect('host', 'user', 'password', 'db')
        self._db_cur = self._db_connection.cursor()

    def query(self, query, params):
        return self._db_cur.execute(query, params)

    def __del__(self):
        self._db_connection.close()

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

โชคดีที่หลามได้ สร้าง API ฐานข้อมูลให้เป็นมาตรฐานดังนั้นสิ่งนี้จะทำงานกับฐานข้อมูลที่สอดคล้องกับคุณทั้งหมด :)


คุณจะหลีกเลี่ยงสิ่งนั้นได้อย่างไรselfในdef query(self,?
samayo

2
กำหนดหลีกเลี่ยง? ตัวเองเป็นสิ่งที่กำหนดว่าเป็นวิธีการอินสแตนซ์มากกว่าวิธีการเรียน ฉันเดาว่าคุณสามารถสร้างฐานข้อมูลเป็นคุณสมบัติแบบคงที่ในชั้นเรียนและจากนั้นใช้วิธีการเรียน (ไม่ต้องการตัวเองที่ใดก็ได้) แต่จากนั้นฐานข้อมูลจะเป็นแบบโกลบอลกับชั้นเรียน
Travis

ใช่เพราะฉันพยายามใช้ตัวอย่างของคุณในการสร้างแบบสอบถามอย่างง่ายdb.query('SELECT ...', var)และมันก็บ่นว่าต้องการอาร์กิวเมนต์ตัวที่สาม
samayo

@samson คุณต้องยกตัวอย่างMyDBวัตถุก่อน:db = MyDB(); db.query('select...', var)
cowbert

ข้อความที่ป้องกันนี้ResourceWarning: unclosed <socket.socket...
Bob Stein

3

ขณะจัดการการเชื่อมต่อฐานข้อมูลมีสองสิ่งที่ต้องคำนึงถึง:

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

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

เพื่อทำให้แนวคิดทั้งหมดเหล่านี้เป็นจริงดูตัวอย่างต่อไปนี้ที่ล้อม psycopg2

import psycopg2


class Postgres(object):
"""docstring for Postgres"""
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            # normally the db_credenials would be fetched from a config file or the enviroment
            # meaning shouldn't be hardcoded as follow
            db_config = {'dbname': 'demo', 'host': 'localhost',
                     'password': 'postgres', 'port': 5432, 'user': 'postgres'}
            try:
                print('connecting to PostgreSQL database...')
                connection = Postgres._instance.connection = psycopg2.connect(**db_config)
                cursor = Postgres._instance.cursor = connection.cursor()
                cursor.execute('SELECT VERSION()')
                db_version = cursor.fetchone()

            except Exception as error:
                print('Error: connection not established {}'.format(error))
                Postgres._instance = None

            else:
                print('connection established\n{}'.format(db_version[0]))

        return cls._instance

    def __init__(self):
        self.connection = self._instance.connection
        self.cursor = self._instance.cursor

    def query(self, query):
        try:
            result = self.cursor.execute(query)
        except Exception as error:
            print('error execting query "{}", error: {}'.format(query, error))
            return None
        else:
            return result

    def __del__(self):
        self.connection.close()
        self.cursor.close()

1
Hi! ขอบคุณสำหรับคำตอบ. if Database._instance is None: NameError: name 'Database' is not definedแต่เมื่อฉันพยายามที่จะใช้มันในกรณีของฉันฉันมี ฉันไม่สามารถเข้าใจสิ่งที่Databaseเป็นและวิธีการแก้ไขนี้
Ilya Rusin

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

ถ้าฉันจะโทรซ้ำPostgres.query(Postgres(), some_sql_query)ในwhileลูปซ้ำ ๆ มันจะยังคงเปิดและปิดการเชื่อมต่อในการวนซ้ำแต่ละครั้งหรือเปิดตลอดการwhileวนซ้ำจนกว่าโปรแกรมจะออกหรือไม่

@Michael คลาสการเชื่อมต่อถูกใช้งานเป็น singleton ดังนั้นมันจะถูกทำให้เป็นอินสแตนซ์เพียงครั้งเดียว แต่โดยรวมแล้วฉันจะแนะนำกับวิธีการโทรที่แนะนำแทนที่จะเริ่มต้นมันในตัวแปร
ponach

1
@ ponach ขอบคุณมันทำสิ่งที่ฉันต้องการเพื่อให้บรรลุ ฉันปรับรหัสของคุณเล็กน้อยและพยายามใช้คำสั่ง UPDATE ในquery()ฟังก์ชั่นของคุณแต่ดูเหมือนว่ามีปัญหากับรหัสของฉันเมื่อฉันเรียกใช้แอพของฉันใน "ขนาน" ฉันทำคำถามแยกต่างหากเกี่ยวกับมัน: softwareengineering.stackexchange.com/questions/399582/…

2

มันน่าสนใจที่จะเสนอความสามารถของตัวจัดการบริบทสำหรับวัตถุของคุณ ซึ่งหมายความว่าคุณสามารถเขียนรหัสเช่นนี้:

class MyClass:
    def __init__(self):
       # connect to DB
    def __enter__(self):
       return self
    def __exit__(self):
       # close the connection

สิ่งนี้จะช่วยให้คุณมีวิธีที่สะดวกในการปิดการเชื่อมโยงกับฐานข้อมูลโดยอัตโนมัติโดยเรียกคลาสโดยใช้คำสั่ง with:

with MyClass() as my_class:
   # do what you need
# at this point, the connection is safely closed.

-1

เป็นเวลานานที่จะคิดเกี่ยวกับเรื่องนี้ วันนี้ฉันพบวิธี ฉันไม่รู้ว่ามันเป็นวิธีที่ดีที่สุด คุณสร้างไฟล์ด้วยชื่อ: conn.py และบันทึกในโฟลเดอร์ /usr/local/lib/python3.5/site-packages/conn/ ฉันใช้ freebsd และนี่คือเส้นทางของโฟลเดอร์แพ็คเกจไซต์ของฉัน ใน conn.py ของฉัน: conn = "dbname = ผู้ใช้ omnivore = รหัสผ่าน postgres = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` และในสคริปต์ที่ฉันต้องการเรียกการเชื่อมต่อฉันเขียน:

นำเข้า psycopg2 นำเข้า psycopg2.extras นำเข้า psycopg2.extensions

จากการเชื่อมต่อการนำเข้า conn ลอง: conn = psycopg2.connect (conn.conn) ยกเว้น: page = "ไม่สามารถเข้าถึงฐานข้อมูล"

cur = conn.cursor ()

และ blah blah ....

ฉันหวังว่ามีประโยชน์นี้

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