Cách xử lý các kết nối cơ sở dữ liệu trong mô-đun thư viện Python


23

Tôi đã tạo một thư viện trong Python có chứa các hàm để truy cập cơ sở dữ liệu. Đây là một thư viện trình bao quanh cơ sở dữ liệu ứng dụng của bên thứ ba, được viết do thực tế là ứng dụng của bên thứ ba không cung cấp API tốt. Bây giờ tôi ban đầu cho phép mỗi hàm mở một kết nối cơ sở dữ liệu trong suốt thời gian của lệnh gọi là ổn, cho đến khi logic chương trình của tôi sử dụng các cuộc gọi lồng nhau đến các hàm mà sau đó tôi sẽ gọi một hàm cụ thể vài nghìn lần. Đây không phải là rất hiệu quả. Cấu hình này cho thấy chi phí hoạt động trong thiết lập kết nối cơ sở dữ liệu - một lần cho mỗi lần gọi hàm. Vì vậy, tôi đã chuyển kết nối mở từ bên trong (các) chức năng sang chính mô-đun, để kết nối cơ sở dữ liệu sẽ được mở khi mô-đun thư viện được nhập. Điều này đã cho tôi một hiệu suất chấp nhận được.

Bây giờ tôi có hai câu hỏi liên quan đến điều này. Đầu tiên, tôi có cần phải lo lắng rằng tôi không còn đóng kết nối cơ sở dữ liệu một cách rõ ràng không và làm cách nào tôi có thể làm điều đó một cách rõ ràng với thiết lập này? Thứ hai, những gì tôi đã làm rơi vào bất cứ nơi nào gần với lĩnh vực thực hành tốt và làm thế nào tôi có thể tiếp cận điều này?


1
Cung cấp một openConnchức năng và khiến người dùng chuyển nó đến từng chức năng họ gọi, theo cách đó họ có thể phạm vi kết nối trong một withtuyên bố hoặc bất cứ điều gì
Daniel Gratzer

1
Tôi đồng ý với jozfeg, xem xét việc tạo một lớp mở kết nối db trong hàm tạo và đóng kết nối khi thoát
Nick Burns

Câu trả lời:


31

Nó thực sự phụ thuộc vào thư viện bạn đang sử dụng. Một số trong số chúng có thể tự đóng kết nối (Lưu ý: Tôi đã kiểm tra thư viện sqlite3 dựng sẵn, nhưng không được). Python sẽ gọi một hàm hủy khi một đối tượng đi ra khỏi phạm vi và các thư viện này có thể thực hiện một hàm hủy đóng các kết nối một cách duyên dáng.

Tuy nhiên, đó có thể không phải là trường hợp! Tôi muốn giới thiệu, như những người khác có trong các ý kiến, để bọc nó trong một đối tượng.

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

Điều này sẽ khởi tạo kết nối cơ sở dữ liệu của bạn khi bắt đầu và đóng nó khi vị trí đối tượng của bạn được khởi tạo nằm ngoài phạm vi. Lưu ý: Nếu bạn khởi tạo đối tượng này ở cấp mô-đun, nó sẽ tồn tại cho toàn bộ ứng dụng của bạn. Trừ khi điều này được dự định, tôi sẽ đề nghị tách các chức năng cơ sở dữ liệu của bạn khỏi các chức năng không phải là cơ sở dữ liệu.

May mắn thay, python đã tiêu chuẩn hóa API cơ sở dữ liệu , vì vậy, nó sẽ hoạt động với tất cả các DB tuân thủ cho bạn :)


Làm thế nào để bạn tránh điều đó selftrong def query(self,?
samayo

2
định nghĩa tránh? Bản thân là những gì định nghĩa đó là một phương thức cá thể, chứ không phải là một phương thức lớp. Tôi đoán bạn có thể tạo cơ sở dữ liệu như một thuộc tính tĩnh trên lớp và sau đó chỉ sử dụng các phương thức lớp (không cần ở bất cứ đâu), nhưng sau đó cơ sở dữ liệu sẽ là toàn cầu cho lớp, không chỉ là khởi tạo riêng lẻ của nó.
Travis

Vâng, bởi vì tôi đã cố gắng sử dụng ví dụ của bạn để thực hiện một truy vấn đơn giản db.query('SELECT ...', var)và nó phàn nàn về việc cần một đối số thứ ba.
samayo

@samson, bạn cần khởi tạo MyDBđối tượng trước:db = MyDB(); db.query('select...', var)
cowbert

Điều này đã ngăn chặn các tin nhắnResourceWarning: unclosed <socket.socket...
Bob Stein

3

Trong khi xử lý các kết nối cơ sở dữ liệu, có hai điều cần quan tâm:

  1. ngăn chặn nhiều kết nối tức thời, cho phép mỗi chức năng mở một kết nối cơ sở dữ liệu được coi là thông lệ xấu, với số lượng phiên cơ sở dữ liệu hạn chế, bạn sẽ hết phiên; ít nhất là giải pháp của bạn sẽ không mở rộng ra, thay vào đó, sử dụng mẫu singleton, lớp của bạn sẽ chỉ được khởi tạo một lần, để biết thêm thông tin về mẫu này, hãy xem liên kết

  2. đóng kết nối khi thoát ứng dụng, giả sử bạn đã không làm và bạn có ít nhất hàng chục ứng dụng đang hoạt động giống nhau, lúc đầu mọi thứ sẽ ổn, nhưng bạn sẽ hết các phiên cơ sở dữ liệu và cách khắc phục duy nhất sẽ khởi động lại máy chủ cơ sở dữ liệu, đây không phải là một điều tốt cho một ứng dụng trực tiếp do đó sử dụng cùng một kết nối bất cứ khi nào có thể.

để củng cố tất cả các khái niệm này, xem ví dụ sau đây kết thúc 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
Chào! Cảm ơn bạn vì câu trả lời. Nhưng khi tôi cố gắng thực hiện nó trong trường hợp của tôi, tôi có if Database._instance is None: NameError: name 'Database' is not defined. Tôi không thể hiểu những gì Databasevà làm thế nào tôi có thể khắc phục điều này.
Ilya Rusin

1
@IlyaRusin đó là lỗi của tôi, trên thực tế Cơ sở dữ liệu chỉ là một lớp cha mẹ trong đó tôi đặt các phương thức chung để xử lý RDBMS khác nhau vì tôi không chỉ kết nối với Postgres. Tuy nhiên, xin lỗi vì sai lầm và tôi hy vọng phiên bản sửa lỗi có thể hữu ích cho bạn, vui lòng thêm, sửa đổi mã theo nhu cầu của bạn nếu bạn có bất kỳ câu hỏi liên quan nào đừng ngần ngại.
ponach

Nếu tôi liên tục gọi Postgres.query(Postgres(), some_sql_query)trong một whilevòng lặp, nó vẫn mở và đóng kết nối trong mỗi lần lặp, hay giữ nó mở trong toàn bộ thời gian của whilevòng lặp cho đến khi chương trình thoát?

@Michael lớp kết nối được triển khai dưới dạng một singleton, do đó nó sẽ được khởi tạo chỉ một lần, nhưng nói chung tôi sẽ đề xuất chống lại cách gọi được đề xuất, thay vào đó bắt đầu nó trong một biến
ponach

1
@ponach Cảm ơn, nó thực hiện chính xác những gì tôi muốn đạt được. Tôi đã điều chỉnh mã của bạn một chút và cố gắng sử dụng câu lệnh CẬP NHẬT trong query()chức năng của bạn , nhưng có vẻ như có vấn đề với mã của tôi khi tôi chạy ứng dụng của mình trong "song song". Tôi đã đặt một câu hỏi riêng về nó: softwareengineering.stackexchange.com/questions/399582/iêu

2

Thật thú vị khi cung cấp khả năng quản lý bối cảnh cho các đối tượng của bạn. Điều này có nghĩa là bạn có thể viết một mã như thế này:

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

Điều này sẽ cung cấp cho bạn một cách thuận tiện để tự động đóng kết nối với cơ sở dữ liệu bằng cách gọi lớp bằng câu lệnh with:

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

-1

Một thời gian dài để suy nghĩ về điều này. Đến hôm nay, tôi đã tìm được đường. tôi không biết đó là cách tốt nhất bạn tạo một tệp có tên: Conn.py và lưu nó trong thư mục /usr/local/lib/python3.5/site-packages/conn/. Tôi sử dụng freebsd và đây là đường dẫn của thư mục gói trang web của tôi. trong phần kết nối của tôi: Conn = "dbname = omnivore user = postgres password = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` và trong kịch bản tôi muốn gọi kết nối, tôi viết:

nhập psycopg2 nhập psycopg2.extras nhập psycopg2.extensions

từ nhập khẩu liên kết thử: Conn = psycopg2.connect (Conn.conn) ngoại trừ: page = "Không thể truy cập cơ sở dữ liệu"

cur = Conn.cthon ()

và blah blah ....

tôi hy vọng điều này hữu ích

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.