python操作mysql之只看這篇就夠了

建議先直奔我的github, 在這里: https://github.com/xiaofeipapa/python_example

滿滿的都是寫好拿來就可以測試的代碼:

image.png

代碼下載下來, 左手雞翅右手鼠標(biāo)點(diǎn)鼠標(biāo)運(yùn)行, 5分鐘學(xué)完, 豈不美哉!

初始化準(zhǔn)備

安裝pymysql 包

sudo pip3 install PyMysql 

然后在mysql里創(chuàng)建數(shù)據(jù)庫名稱為 my_test, 用戶名/密碼也是 my_test , 并創(chuàng)建 Product 表如下:

    DROP TABLE IF EXISTS `Product`;
    /*!40101 SET @saved_cs_client     = @@character_set_client */;
    /*!40101 SET character_set_client = utf8 */;
    CREATE TABLE `Product` (
      `id` int NOT NULL AUTO_INCREMENT,
      `name` varchar(40) NOT NULL,    /* 商品名稱 */
      `remark` varchar(1000) NULL,
      `isBuy` int(1) DEFAULT 1,      /* 1: 在售 2:賣出 */
      `version` int(11) NOT null default 1000, 
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8;

測試

pymysql 的代碼還是很簡單的, 以下代碼分別為連接mysql 獲得connection, 從connection 獲得cursor 進(jìn)行操作, 都是固定套路:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.


"""
import pymysql


host = 'localhost'
port = 3306
db = 'mysql_test'
user = 'mysql_test'
password = 'mysql_test'


# ---- 用pymysql 操作數(shù)據(jù)庫
def get_connection():
    conn = pymysql.connect(host=host, port=port, db=db, user=user, password=password)
    return conn


def check_it():

    conn = get_connection()

    # 使用 cursor() 方法創(chuàng)建一個(gè) dict 格式的游標(biāo)對象 cursor
    cursor = conn.cursor(pymysql.cursors.DictCursor)

    # 使用 execute()  方法執(zhí)行 SQL 查詢
    cursor.execute("select count(id) as total from Product")

    # 使用 fetchone() 方法獲取單條數(shù)據(jù).
    data = cursor.fetchone()

    print("-- 當(dāng)前數(shù)量: %d " % data['total'])

    # 關(guān)閉數(shù)據(jù)庫連接
    cursor.close()
    conn.close()


if __name__ == '__main__':
    check_it()

pymysql 實(shí)戰(zhàn)應(yīng)用

使用with 優(yōu)化操作代碼

從以上代碼可以看到, 如果每次都要打開連接, 關(guān)閉連接 .... 代碼難看且容易出錯(cuò). 最好的辦法是用 python with 的方式來增加一個(gè)上下文管理器. 修改如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
import pymysql
from timeit import default_timer


host = 'localhost'
port = 3306
db = 'mysql_test'
user = 'mysql_test'
password = 'mysql_test'


# ---- 用pymysql 操作數(shù)據(jù)庫
def get_connection():
    conn = pymysql.connect(host=host, port=port, db=db, user=user, password=password)
    return conn


# ---- 使用 with 的方式來優(yōu)化代碼
class UsingMysql(object):

    def __init__(self, commit=True, log_time=True, log_label='總用時(shí)'):
        """

        :param commit: 是否在最后提交事務(wù)(設(shè)置為False的時(shí)候方便單元測試)
        :param log_time:  是否打印程序運(yùn)行總時(shí)間
        :param log_label:  自定義log的文字
        """
        self._log_time = log_time
        self._commit = commit
        self._log_label = log_label

    def __enter__(self):

        # 如果需要記錄時(shí)間
        if self._log_time is True:
            self._start = default_timer()

        # 在進(jìn)入的時(shí)候自動獲取連接和cursor
        conn = get_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        conn.autocommit = False

        self._conn = conn
        self._cursor = cursor
        return self

    def __exit__(self, *exc_info):
        # 提交事務(wù)
        if self._commit:
            self._conn.commit()
        # 在退出的時(shí)候自動關(guān)閉連接和cursor
        self._cursor.close()
        self._conn.close()

        if self._log_time is True:
            diff = default_timer() - self._start
            print('-- %s: %.6f 秒' % (self._log_label, diff))

    @property
    def cursor(self):
        return self._cursor

def check_it():

    with UsingMysql(log_time=True) as um:
        um.cursor.execute("select count(id) as total from Product")
        data = um.cursor.fetchone()
        print("-- 當(dāng)前數(shù)量: %d " % data['total'])

if __name__ == '__main__':
    check_it()

程序運(yùn)行結(jié)果如下:

-- 當(dāng)前數(shù)量: 0 
-- 用時(shí): 0.002345 秒

用這種方式改寫代碼之后, 業(yè)務(wù)方法更精簡. 并且加了參數(shù)方便進(jìn)行單元測試和監(jiān)控代碼的運(yùn)行時(shí)間, 不亦美哉.

封裝公用代碼

現(xiàn)在新增一個(gè)pymysql_comm.py 類, 將連接代碼和寫好的UsingMysql 放進(jìn)去, 代碼如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
import pymysql
from timeit import default_timer


host = 'localhost'
port = 3306
db = 'mysql_test'
user = 'mysql_test'
password = 'mysql_test'


# ---- 用pymysql 操作數(shù)據(jù)庫
def get_connection():
    conn = pymysql.connect(host=host, port=port, db=db, user=user, password=password)
    return conn


# ---- 使用 with 的方式來優(yōu)化代碼
class UsingMysql(object):

    def __init__(self, commit=True, log_time=True, log_label='總用時(shí)'):
        """

        :param commit: 是否在最后提交事務(wù)(設(shè)置為False的時(shí)候方便單元測試)
        :param log_time:  是否打印程序運(yùn)行總時(shí)間
        :param log_label:  自定義log的文字
        """
        self._log_time = log_time
        self._commit = commit
        self._log_label = log_label

    def __enter__(self):

        # 如果需要記錄時(shí)間
        if self._log_time is True:
            self._start = default_timer()

        # 在進(jìn)入的時(shí)候自動獲取連接和cursor
        conn = get_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        conn.autocommit = False

        self._conn = conn
        self._cursor = cursor
        return self

    def __exit__(self, *exc_info):
        # 提交事務(wù)
        if self._commit:
            self._conn.commit()
        # 在退出的時(shí)候自動關(guān)閉連接和cursor
        self._cursor.close()
        self._conn.close()

        if self._log_time is True:
            diff = default_timer() - self._start
            print('-- %s: %.6f 秒' % (self._log_label, diff))

    @property
    def cursor(self):
        return self._cursor


新增一個(gè) test.py 文件, 引入這個(gè)模塊進(jìn)行測試使用. 代碼如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
from pymysql_comm import UsingMysql


def check_it():

    with UsingMysql(log_time=True) as um:
        um.cursor.execute("select count(id) as total from Product")
        data = um.cursor.fetchone()
        print("-- 當(dāng)前數(shù)量: %d " % data['total'])

if __name__ == '__main__':
    check_it()

后續(xù)的學(xué)習(xí)和開發(fā)都可以使用這個(gè)封裝類, 用類似test.py的方式來寫自己的業(yè)務(wù)代碼, 更方便精簡了.

增刪改查api

下面記錄了最常用的增刪改查分頁等方法

新增單條記錄

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
from pymysql_comm import UsingMysql


def select_one(cursor):
    cursor.execute("select * from Product")
    data = cursor.fetchone()
    print("-- 單條記錄: {0} ".format(data))


# 新增單條記錄
def create_one():

    with UsingMysql(log_time=True) as um:
        sql = "insert into Product(name, remark) values(%s, %s)"
        params = ('男士雙肩背包1', '這個(gè)是非常好的背包')
        um.cursor.execute(sql, params)

        # 查看結(jié)果
        select_one(um.cursor)

if __name__ == '__main__':
    create_one()


在上面代碼里先增加了一條記錄, 然后接著查看這條記錄, 結(jié)果類似這樣:

-- 單條記錄: {'id': 1003, 'name': '男士雙肩背包1', 'isBuy': 1, 'remark': '這個(gè)是非常好的背包'} 
-- 用時(shí): 0.002600 秒

順便吐嘈下, 用1秒/0.0026 可計(jì)算得出并發(fā)數(shù)是 384.6 , 這表示無優(yōu)化狀態(tài)下每秒插入記錄 384 條左右, 性能比較低.

新增多條記錄

一口氣插入1000條記錄, 同時(shí)加入查詢方法, 如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
from pymysql_comm import UsingMysql


def get_count(cursor):
    cursor.execute("select count(id) as total from Product")

    # 使用 fetchone() 方法獲取單條數(shù)據(jù).
    data = cursor.fetchone()

    print("-- 當(dāng)前數(shù)量: %d " % data['total'])


def delete_all(cursor):
    cursor.execute("delete from Product")


# 插入 1000 條記錄
def create_many():

    with UsingMysql(log_time=True) as um:

        # 清空之前的測試記錄
        delete_all(um.cursor)

        for i in range(0, 1000):

            sql = "insert into Product(name, remark) values(%s, %s)"
            params = ('男士雙肩背包%d' % i, '這個(gè)是非常好的背包%d' %i)
            um.cursor.execute(sql, params)

        # 查看結(jié)果
        get_count(um.cursor)

if __name__ == '__main__':
    create_many()

在我的機(jī)器用時(shí)如下:

-- 當(dāng)前數(shù)量: 1000 
-- 用時(shí): 0.097566 秒

勉強(qiáng)能接受. 現(xiàn)在用你的mysql 客戶端查看數(shù)據(jù)庫, 應(yīng)該能看到1000條數(shù)據(jù):


image.png

刪除某條記錄

為了方便測試, 順便把查的方法也提前寫出來了. 代碼如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
from pymysql_comm import UsingMysql


def delete_one(cursor, name):
    sql = 'delete from Product where name = %s'
    params = name
    cursor.execute(sql, params)
    print('--- 已刪除名字為%s的商品. ' % name)


def select_one(cursor):
    sql = 'select * from Product'
    cursor.execute(sql)
    data = cursor.fetchone()
    print('--- 已找到名字為%s的商品. ' % data['name'])
    return data['name']


def select_one_by_name(cursor, name):
    sql = 'select * from Product where name = %s'
    params = name
    cursor.execute(sql, params)
    data = cursor.fetchone()
    if data:
        print('--- 已找到名字為%s的商品. ' % data['name'])
    else:
        print('--- 名字為%s的商品已經(jīng)沒有了' % name)


# 刪除單條記錄
def check_delete_one():

    with UsingMysql(log_time=True) as um:

        # 查找一條記錄
        name = select_one(um.cursor)

        # 刪除之
        delete_one(um.cursor, name)

        # 查看還在不在?
        select_one_by_name(um.cursor, name)

if __name__ == '__main__':
    check_delete_one()

操作結(jié)果類似這樣:

--- 已找到名字為男士雙肩背包0的商品. 
--- 已刪除名字為男士雙肩背包0的商品. 
--- 名字為男士雙肩背包0的商品已經(jīng)沒有了
-- 用時(shí): 0.015917 秒

修改記錄

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
from pymysql_comm import UsingMysql


def update_by_pk(cursor, name, pk):
    sql = "update Product set name = '%s' where id = %d" % (name, pk)

    cursor.execute(sql)


def select_one(cursor):
    sql = 'select * from Product'
    cursor.execute(sql)
    return cursor.fetchone()


def select_one_by_name(cursor, name):
    sql = 'select * from Product where name = %s'
    params = name
    cursor.execute(sql, params)
    data = cursor.fetchone()
    if data:
        print('--- 已找到名字為%s的商品. ' % data['name'])
    else:
        print('--- 名字為%s的商品已經(jīng)沒有了' % name)


# 修改記錄
def check_update():

    with UsingMysql(log_time=True) as um:

        # 查找一條記錄
        data = select_one(um.cursor)
        pk = data['id']
        print('--- 商品{0}: '.format(data))

        # 修改名字
        new_name = '單肩包'
        update_by_pk(um.cursor, new_name, pk)

        # 查看
        select_one_by_name(um.cursor, new_name)

if __name__ == '__main__':
    check_update()


這里記錄了根據(jù)id修改記錄的方法, 其他修改方式主要看sql 知識, 就不再贅述.

查找

查找主要涉及pymysql 的fetchone(返回單條數(shù)據(jù)), fetchall(返回所有數(shù)據(jù)) . fetchone 上面已經(jīng)寫過了, 現(xiàn)在來看看fetchall 方法:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
from pymysql_comm import UsingMysql


def fetch_list_by_filter(cursor, pk):
    sql = 'select * from Product where id > %d' % pk
    cursor.execute(sql)
    data_list = cursor.fetchall()
    print('-- 總數(shù): %d' % len(data_list))
    return data_list


# 查找
def fetch_list():

    with UsingMysql(log_time=True) as um:

        # 查找id 大于800的記錄
        data_list = fetch_list_by_filter(um.cursor, 800)

        # 查找id 大于 10000 的記錄
        data_list = fetch_list_by_filter(um.cursor, 10000)

if __name__ == '__main__':
    fetch_list()

結(jié)果應(yīng)該類似這樣:

-- 總數(shù): 999
-- 總數(shù): 0
-- 用時(shí): 0.012355 秒

分頁查詢

分頁查詢主要是用了mysql 的limit 特性, 和pymysql 沒太大關(guān)系, 代碼如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
from pymysql_comm import UsingMysql


def fetch_page_data(cursor, pk, page_size, skip):
    sql = 'select * from Product where id > %d limit %d,%d' % (pk, skip, page_size)
    cursor.execute(sql)
    data_list = cursor.fetchall()
    print('-- 總數(shù): %d' % len(data_list))
    print('-- 數(shù)據(jù): {0}'.format(data_list))
    return data_list


# 查找
def check_page():

    with UsingMysql(log_time=True) as um:

        page_size = 10
        pk = 500

        for page_no in range(1, 6):

            print('====== 第%d頁數(shù)據(jù)' % page_no)
            skip = (page_no - 1) * page_size

            fetch_page_data(um.cursor, pk, page_size, skip)


if __name__ == '__main__':
    check_page()

上面列出了5頁數(shù)據(jù). 看起來大概是這樣:


image.png

中級篇: 使用連接池和封裝方法

經(jīng)過一系列示例, 現(xiàn)在你應(yīng)該會用pymysql 做最基本的增刪改查分頁了. 現(xiàn)在來看點(diǎn)高級點(diǎn)的功能: 更好的封裝代碼和使用數(shù)據(jù)庫連接池.

封裝代碼

我們發(fā)覺調(diào)用pymysql的代碼都差不多, 其實(shí)可以挪到公用方法里去, 新增一個(gè) pymysql_lib_1.py 文件, 實(shí)現(xiàn)UsingMysql 如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
import pymysql
from timeit import default_timer


host = 'localhost'
port = 3306
db = 'mysql_test'
user = 'mysql_test'
password = 'mysql_test'


# ---- 用pymysql 操作數(shù)據(jù)庫
def get_connection():
    conn = pymysql.connect(host=host, port=port, db=db, user=user, password=password)
    return conn


# ---- 使用 with 的方式來優(yōu)化代碼
class UsingMysql(object):

    def __init__(self, commit=True, log_time=True, log_label='總用時(shí)'):
        """

        :param commit: 是否在最后提交事務(wù)(設(shè)置為False的時(shí)候方便單元測試)
        :param log_time:  是否打印程序運(yùn)行總時(shí)間
        :param log_label:  自定義log的文字
        """
        self._log_time = log_time
        self._commit = commit
        self._log_label = log_label

    def __enter__(self):

        # 如果需要記錄時(shí)間
        if self._log_time is True:
            self._start = default_timer()

        # 在進(jìn)入的時(shí)候自動獲取連接和cursor
        conn = get_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        conn.autocommit = False

        self._conn = conn
        self._cursor = cursor
        return self

    def __exit__(self, *exc_info):
        # 提交事務(wù)
        if self._commit:
            self._conn.commit()
        # 在退出的時(shí)候自動關(guān)閉連接和cursor
        self._cursor.close()
        self._conn.close()

        if self._log_time is True:
            diff = default_timer() - self._start
            print('-- %s: %.6f 秒' % (self._log_label, diff))

    # ========= 一系列封裝的業(yè)務(wù)方法

    # 返回 count
    def get_count(self, sql, params=None, count_key='count(id)'):
        self.cursor.execute(sql, params)
        data = self.cursor.fetchone()
        if not data:
            return 0
        return data[count_key]

    def fetch_one(self, sql, params=None):
        self.cursor.execute(sql, params)
        return self.cursor.fetchone()

    def fetch_all(self, sql, params=None):
        self.cursor.execute(sql, params)
        return self.cursor.fetchall()

    def fetch_by_pk(self, sql, pk):
        self.cursor.execute(sql, (pk,))
        return self.cursor.fetchall()

    def update_by_pk(self, sql, params=None):
        self.cursor.execute(sql, params)

    @property
    def cursor(self):
        return self._cursor


然后新增一個(gè)test2.py 文件進(jìn)行測試, 如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
from pymysql_lib_1 import UsingMysql


def check_it():

    with UsingMysql(log_time=True) as um:
        sql = "select count(id) as total from Product"
        print("-- 當(dāng)前數(shù)量: %d " % um.get_count(sql, None, 'total'))

if __name__ == '__main__':
    check_it()

可以看到業(yè)務(wù)代碼精簡了不少, 拼sql 和參數(shù)就好了, 其他調(diào)用方法都封裝到了上下文管理器.

使用連接池

在上面的使用過程中, 每個(gè)請求都會開啟一個(gè)數(shù)據(jù)庫連接. 如果連接數(shù)太多, 數(shù)據(jù)庫很快就會報(bào)錯(cuò). 如何調(diào)整數(shù)據(jù)庫的連接數(shù)增加并發(fā)性能算是個(gè)比較有技術(shù)含量的話題, 我打算放到高級篇里再介紹. 現(xiàn)在這里要讓你知道的是: 數(shù)據(jù)庫這么返回連接是不行的, 必須要使用連接池.

連接池代碼當(dāng)然不用自己動手, python的世界那么大~ 先安裝DBUtils, 如下:

pip3 install DBUtils

然后新增 pymysql_lib.py , 增加代碼如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""
import pymysql
from timeit import default_timer
from DBUtils.PooledDB import PooledDB


class DMysqlConfig:
    """

        :param mincached:連接池中空閑連接的初始數(shù)量
        :param maxcached:連接池中空閑連接的最大數(shù)量
        :param maxshared:共享連接的最大數(shù)量
        :param maxconnections:創(chuàng)建連接池的最大數(shù)量
        :param blocking:超過最大連接數(shù)量時(shí)候的表現(xiàn),為True等待連接數(shù)量下降,為false直接報(bào)錯(cuò)處理
        :param maxusage:單個(gè)連接的最大重復(fù)使用次數(shù)
        :param setsession:optional list of SQL commands that may serve to prepare
            the session, e.g. ["set datestyle to ...", "set time zone ..."]
        :param reset:how connections should be reset when returned to the pool
            (False or None to rollback transcations started with begin(),
            True to always issue a rollback for safety's sake)
        :param host:數(shù)據(jù)庫ip地址
        :param port:數(shù)據(jù)庫端口
        :param db:庫名
        :param user:用戶名
        :param passwd:密碼
        :param charset:字符編碼
    """

    def __init__(self, host, db, user, password, port=3306):
        self.host = host
        self.port = port
        self.db = db
        self.user = user
        self.password = password

        self.charset = 'UTF8'  # 不能是 utf-8
        self.minCached = 10
        self.maxCached = 20
        self.maxShared = 10
        self.maxConnection = 100

        self.blocking = True
        self.maxUsage = 100
        self.setSession = None
        self.reset = True


# ---- 用連接池來返回?cái)?shù)據(jù)庫連接
class DMysqlPoolConn:

    __pool = None

    def __init__(self, config):

        if not self.__pool:
            self.__class__.__pool = PooledDB(creator=pymysql,
                                             maxconnections=config.maxConnection,
                                             mincached=config.minCached,
                                             maxcached=config.maxCached,
                                             maxshared=config.maxShared,
                                             blocking=config.blocking,
                                             maxusage=config.maxUsage,
                                             setsession=config.setSession,
                                             charset=config.charset,
                                             host=config.host,
                                             port=config.port,
                                             database=config.db,
                                             user=config.user,
                                             password=config.password,
                                             )

    def get_conn(self):
        return self.__pool.connection()


# ========== 在程序的開始初始化一個(gè)連接池
host = 'localhost'
port = 3306
db = 'mysql_test'
user = 'mysql_test'
password = 'mysql_test'

db_config = DMysqlConfig(host, db, user, password, port)


g_pool_connection = DMysqlPoolConn(db_config)


# ---- 使用 with 的方式來優(yōu)化代碼
class UsingMysql(object):

    def __init__(self, commit=True, log_time=True, log_label='總用時(shí)'):
        """

        :param commit: 是否在最后提交事務(wù)(設(shè)置為False的時(shí)候方便單元測試)
        :param log_time:  是否打印程序運(yùn)行總時(shí)間
        :param log_label:  自定義log的文字
        """
        self._log_time = log_time
        self._commit = commit
        self._log_label = log_label

    def __enter__(self):

        # 如果需要記錄時(shí)間
        if self._log_time is True:
            self._start = default_timer()

        # 從連接池獲取數(shù)據(jù)庫連接
        conn = g_pool_connection.get_conn()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        conn.autocommit = False

        self._conn = conn
        self._cursor = cursor
        return self

    def __exit__(self, *exc_info):
        # 提交事務(wù)
        if self._commit:
            self._conn.commit()
        # 在退出的時(shí)候自動關(guān)閉連接和cursor
        self._cursor.close()
        self._conn.close()

        if self._log_time is True:
            diff = default_timer() - self._start
            print('-- %s: %.6f 秒' % (self._log_label, diff))

    # ========= 一系列封裝的業(yè)務(wù)方法

    # 返回 count
    def get_count(self, sql, params=None, count_key='count(id)'):
        self.cursor.execute(sql, params)
        data = self.cursor.fetchone()
        if not data:
            return 0
        return data[count_key]

    def fetch_one(self, sql, params=None):
        self.cursor.execute(sql, params)
        return self.cursor.fetchone()

    def fetch_all(self, sql, params=None):
        self.cursor.execute(sql, params)
        return self.cursor.fetchall()

    def fetch_by_pk(self, sql, pk):
        self.cursor.execute(sql, (pk,))
        return self.cursor.fetchall()

    def update_by_pk(self, sql, params=None):
        self.cursor.execute(sql, params)

    @property
    def cursor(self):
        return self._cursor

新增加的一大坨代碼看起來很多, 其實(shí)只是增加了兩個(gè)配置類. 同時(shí)在這里:

# ========== 在程序的開始初始化一個(gè)連接池
host = 'localhost'
port = 3306
db = 'mysql_test'
user = 'mysql_test'
password = 'mysql_test'

db_config = DMysqlConfig(host, db, user, password, port)


g_pool_connection = DMysqlPoolConn(db_config)

實(shí)例化了連接池. 后續(xù)的上下文管理器改從連接池獲取連接, 其他代碼都不變.

把這個(gè)pymysql_lib 存好, 以后有機(jī)會慢慢增加/修改里面的各種fetch/update ... 方法, 這個(gè)文件會變成你的傳家寶, 你會用它和mysql 打交道很多很多年...

最后的嚴(yán)肅問題: raw sql ? 使用或放棄?

從UsingMysql 可以看出代碼優(yōu)化到這個(gè)層面已經(jīng)到頭了. 可是那些什么insert 語句, update 語句還是要拼一大堆sql 字段, 怎么辦? 這里有兩個(gè)辦法: 一個(gè)是思考一些代碼生成技術(shù), 根據(jù)各種參數(shù)自動組裝sql, 這樣下去這代碼就會變成自己獨(dú)有的orm了(年輕時(shí)我就這么干) . 另一個(gè)選擇(也就是我現(xiàn)在的選擇), 不用pymysql, 而是使用sqlalchemy .... :-D :-D :-D

我現(xiàn)在工作中很少用Mysql , 通常用到的時(shí)候都是接手別人的代碼. 所以我一般這么做: 簡單無性能瓶頸的業(yè)務(wù)代碼, 我用sqlalchemy 不用動腦子. 有性能瓶頸的地方, 我用pymysql原生sql進(jìn)行操作. 因?yàn)閜ymysql 網(wǎng)上很少成型的好文章, 所以我才寫了這么一大坨進(jìn)行總結(jié).

sqlchemy 入門

新增一個(gè) sqlal_comm.py 類, 代碼如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""


from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index, Text
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import create_engine
from timeit import default_timer


host = 'localhost'
port = 3306
db = 'mysql_test'
user = 'mysql_test'
password = 'mysql_test'


g_mysql_url = 'mysql+pymysql://%s:%s@%s:%d/%s' % (user, password, host, port, db)


engine = create_engine(g_mysql_url)

Base = declarative_base()


class Product(Base):
    __tablename__ = 'Product'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(40))
    remark = Column(String(1000), nullable=True)
    isBuy = Column(Integer, default=1)

Base.metadata.create_all(engine)  #創(chuàng)建表

Session = sessionmaker(bind=engine)

# =============== 以上為初始化數(shù)據(jù)庫和表

# ---- 使用 with 的方式來優(yōu)化代碼
class UsingAlchemy(object):

    def __init__(self, commit=True, log_time=True, log_label='總用時(shí)'):
        """

        :param commit: 是否在最后提交事務(wù)(設(shè)置為False的時(shí)候方便單元測試)
        :param log_time:  是否打印程序運(yùn)行總時(shí)間
        :param log_label:  自定義log的文字
        """
        self._log_time = log_time
        self._commit = commit
        self._log_label = log_label
        self._session = Session()

    def __enter__(self):

        # 如果需要記錄時(shí)間
        if self._log_time is True:
            self._start = default_timer()

        return self

    def __exit__(self, *exc_info):
        # 提交事務(wù)
        if self._commit:
            self._session.commit()

        if self._log_time is True:
            diff = default_timer() - self._start
            print('-- %s: %.6f 秒' % (self._log_label, diff))

    @property
    def session(self):
        return self._session

這個(gè)文件分為兩大部分: 上部分是 sqlalchemy 的固定套路: 拼接連接字符串, 進(jìn)行連接初始化, 然后初始化數(shù)據(jù)庫的表. 下部分是繼續(xù)之前的上下文管理套路, 讓代碼編寫更輕松點(diǎn).

新增一個(gè)test4.py 進(jìn)行測試, 如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""

from sqlal_comm import Session, Product, UsingAlchemy


# 測試獲取一條記錄
def check_it():

    session = Session()

    result = session.query(Product).first()
    if result is None:
        session.commit()
        return None

    session.commit()
    session.close()
    print('-- 得到記錄: {0}'.format(result))


# 測試獲取一條記錄
def check_it_2():

    with UsingAlchemy() as ua:

        result = ua.session.query(Product).first()
        print('-- 得到記錄: {0}'.format(result))

if __name__ == '__main__':
    check_it()
    check_it_2()

這個(gè)文件用兩種方式來進(jìn)行調(diào)用, 顯然用了上下文管理的方式會更輕松點(diǎn).

sqlalchemy 條件查詢和分頁

有一篇博客極好, 把增刪改查總結(jié)得明明白白. 所以我也偷懶了, 在最后直接放出他的文章. 我這里來補(bǔ)充兩個(gè)他沒有寫的: 條件查詢和分頁查詢.

條件查詢

主要的業(yè)務(wù)場景就是: 用戶傳入多個(gè)參數(shù), 要根據(jù)參數(shù)的不同構(gòu)造不同的查詢條件. 新增一個(gè)python文件, 如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""

from sqlal_comm import Session, Product, UsingAlchemy

is_available = 1


# 重新插入數(shù)據(jù)
def re_insert_data():

    with UsingAlchemy() as ua:

        # 刪除所有數(shù)據(jù)
        ua.session.query(Product).filter(Product.id > 0).delete()

        data_list = []
        for i in range(0, 10):

            data = Product()
            data.name = '雙肩包%d' % i
            data.remark = '雙肩包%d' % i
            data.isBuy = is_available
            data_list.append(data)

        # 批量增加數(shù)據(jù)
        ua.session.add_all(data_list)


# 測試條件查詢
def check_search(keyword):

    re_insert_data()

    with UsingAlchemy() as ua:

        # 多條件的列表組合
        query_filter = []
        if keyword:
            like_value = '%{}%'.format(keyword)

            # 查詢 name 和 remark 字段里 包含查詢關(guān)鍵詞的記錄
            query_filter.append(Product.name.like(like_value) | Product.remark.like(like_value))

        # 增加另一個(gè)查詢條件作為測試
        query_filter.append(Product.isBuy == is_available)

        # 查找結(jié)果
        data_list = ua.session.query(Product).filter(*query_filter).all()
        print('-- 記錄條數(shù): {}'.format(len(data_list)))
        print('-- 該記錄是: %s' % data_list[0].name)

if __name__ == '__main__':
    check_search(3)


這個(gè)文件的演示分兩步:

  1. 刪除原來數(shù)據(jù)庫的數(shù)據(jù), 插入10條新的測試數(shù)據(jù). 這樣能確??隙ㄓ幸粭l帶關(guān)鍵字3的數(shù)據(jù).
  2. 演示了如何組合查詢條件進(jìn)行查找, 其中有一個(gè)帶or的查找條件. 從這個(gè)例子入手, 所有查詢對你都不是難題了.

程序運(yùn)行結(jié)果應(yīng)該類似這樣:

-- 總用時(shí): 0.009106 秒
-- 記錄條數(shù): 1
-- 該記錄是: 雙肩包3
-- 總用時(shí): 0.001323 秒

分頁查找

增加一個(gè)新的python文件, 代碼如下:

#! /usr/bin/python
# -*- coding: UTF-8 -*-

"""

    作者: 小肥巴巴
    簡書: http://www.lxweimin.com/u/db796a501972
    郵箱: imyunshi@163.com
    github: https://github.com/xiaofeipapa/python_example

    您可以任意轉(zhuǎn)載, 懇請保留我作為原作者, 謝謝.

"""

from sqlal_comm import Session, Product, UsingAlchemy

is_available = 1


# 重新插入數(shù)據(jù)
def re_insert_data():

    with UsingAlchemy() as ua:

        # 刪除所有數(shù)據(jù)
        ua.session.query(Product).filter(Product.id > 0).delete()

        data_list = []
        for i in range(0, 10):

            data = Product()
            data.name = '雙肩包%d' % i
            data.remark = '雙肩包%d' % i
            data.isBuy = is_available
            data_list.append(data)

        # 批量增加數(shù)據(jù)
        ua.session.add_all(data_list)


# 測試分頁查找
def check_search(page_no, page_size):

    re_insert_data()

    with UsingAlchemy() as ua:

        # 多條件的列表組合
        query_filter = list()

        # 增加另一個(gè)查詢條件作為測試
        query_filter.append(Product.isBuy == is_available)

        offset = (page_no - 1) * page_size

        # 查找結(jié)果
        data_list = ua.session.query(Product).filter(*query_filter).limit(page_size).offset(offset).all()
        print('=== 記錄條數(shù): {}'.format(len(data_list)))
        for data in data_list:
            print('-- 記錄: ' + data.name)

if __name__ == '__main__':

    page_size = 5
    for page_no in range(1, 3):
        check_search(page_no, page_size)


可以看到分頁查找就是在獲取列表之前調(diào)用limit 和 offset 方法, 也就是這句:

data_list = ua.session.query(Product).filter(*query_filter).limit(page_size).offset(offset).all()

所以 , 也是很簡單的.

sqlalchemy 增刪改查總結(jié)

這篇文章寫得很好了, 看這里吧: https://www.cnblogs.com/pycode/p/mysql-orm.html

最后, 這次真的是最后了

python 使用mysql 的基礎(chǔ)知識就總結(jié)到這了. 等有時(shí)間我再寫關(guān)于事務(wù)鎖和優(yōu)化并發(fā)性能的高級篇.
本文的完整代碼放在我的github, 在這里: https://github.com/xiaofeipapa/python_example

少年, 記得去給個(gè)star啊~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。