建議先直奔我的github, 在這里: https://github.com/xiaofeipapa/python_example
滿滿的都是寫好拿來就可以測試的代碼:
代碼下載下來, 左手雞翅右手鼠標(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ù):
刪除某條記錄
為了方便測試, 順便把查的方法也提前寫出來了. 代碼如下:
#! /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ù). 看起來大概是這樣:
中級篇: 使用連接池和封裝方法
經(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è)文件的演示分兩步:
- 刪除原來數(shù)據(jù)庫的數(shù)據(jù), 插入10條新的測試數(shù)據(jù). 這樣能確??隙ㄓ幸粭l帶關(guān)鍵字3的數(shù)據(jù).
- 演示了如何組合查詢條件進(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啊~