目錄html
Python 提供了程序的DB-API,支持衆多數據庫的操做。因爲目前使用最多的數據庫爲MySQL,因此咱們這裏以Python操做MySQL爲例子,同時也由於有成熟的API,因此咱們沒必要去關注使用什麼數據,由於操做邏輯和方法是相同的。python
Python 程序想要操做數據庫,首先須要安裝 模塊 來進行操做,Python 2 中流行的模塊爲 MySQLdb,而該模塊在Python 3 中將被廢棄,而使用PyMySQL,這裏以PyMySQL模塊爲例。下面使用pip命令安裝PyMSQL模塊mysql
pip3 install pymysql
若是沒有pip3命令那麼須要確認環境變量是否有添加,安裝完畢後測試是否安裝完畢。sql
lidaxindeMacBook-Pro:~ DahlHin$ python3 Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import pymysql >>> # 若是沒有報錯,則表示安裝成功
首先咱們須要手動安裝一個MySQL數據庫,這裏再也不贅述,參考博文:http://www.cnblogs.com/dachenzi/articles/7159510.html數據庫
鏈接數據庫並執行sql語句的通常流程是:編程
對應到代碼上的邏輯爲:緩存
使用pymysql.connect
方法來鏈接數據庫安全
import pymysql pymysql.connect(host=None, user=None, password="", database=None, port=0, unix_socket=None, charset=''......)
主要的參數有:服務器
調用connect函數,將建立一個數據庫鏈接並獲得一個Connection對象,Connection對象定義了不少的方法和異常。session
host = '10.0.0.13' port = 3306 user = 'dahl' password = '123456' database = 'test' conn = pymysql.connect(host, user, password, database, port) print(conn) # <pymysql.connections.Connection object at 0x000001ABD3063550> conn.ping() # 沒有返回值,沒法鏈接會提示異常
這裏conn就是一個Connection對象,它具備一下屬性和方法:
begin
:開始事務commit
:提交事務rollback
:回滾事務cursor
:返回一個Cursor對象autocommit
:設置事務是否自動提交set_character_set
:設置字符集編碼get_server_info
:獲取數據庫版本信息ping(reconnect=True)
: 測試數據庫是否活着,reconnect表示斷開與服務器鏈接後是否重連,鏈接關閉時拋出異常(通常用來測通斷) 在實際的編程過程當中,通常不會直接調用begin、commit和rollback函數,而是經過上下文管理器實現事務的提交與回滾操做
。
遊標是系統爲用戶開設的一個數據緩存區,存放SQL語句執行的結果,用戶能夠用SQL語句逐一從遊標中獲取記錄,並賦值給變量,交由Python進一步處理。
在數據庫中,遊標是一個十分重要的概念。遊標提供了一種對從表中檢索出的數據進行操做的靈活手段,就本質而言,遊標其實是一種能從包括多條數據記錄的結果集中每次提取一條記錄的機制。遊標老是與一條SQL 選擇語句相關聯由於遊標由結果集(能夠是零條、一條或由相關的選擇語句檢索出的多條記錄)和結果集中指向特定記錄的遊標位置組成。當決定對結果集進行處理時,必須聲明一個指向該結果集的遊標。
正如前面咱們使用Python對文件進行處理,那麼遊標就像咱們打開文件所獲得的文件句柄同樣,只要文件打開成功,該文件句柄就可表明該文件。對於遊標而言,其道理是相同的。
在進行數據庫的操做以前須要建立一個遊標對象,來執行sql語句。
cursor = conn.cursor()
下面利用遊標來執行sql語句:
import pymysql def connect_mysql(): db_config = { 'host':'127.0.0.1', 'port':3306, 'user':'root', 'password':'abc.123', 'charset':'utf8' } return pymysql.connect(**db_config) if __name__ == '__main__': conn = connect_mysql() cursor = conn.cursor() # 建立遊標 sql = r" select user,host from mysql.user " # 要執行的sql語句 cursor.execute(sql) # 交給 遊標執行 result = cursor.fetchall() # 獲取遊標執行結果 print(result)
Connection對象包含以下三個方法用於事務管理:
import pymysql def connect_mysql(): db_config = { 'host': '10.0.0.13', 'port': 3306, 'user': 'dahl', 'password': '123456', 'charset': 'utf8' } return pymysql.connect(**db_config) if __name__ == '__main__': conn = connect_mysql() cursor = conn.cursor() # 建立遊標 conn.begin() sql = r"insert into test.student (id,name,age) VALUES (5,'dahl',23)" # 要執行的sql語句 res = cursor.execute(sql) # 交給 遊標執行 print(res) conn.commit()
這樣使用是極其不安全的,由於sql語句有可能執行失敗,當失敗時,咱們應該進行回滾
if __name__ == '__main__': conn = None cursor = None try: conn = connect_mysql() cursor = conn.cursor() # 建立遊標 sql = r"insert into test.student (id,name,age) VALUES (6,'dahl',23)" # 要執行的sql語句 cursor.execute(sql) # 交給 遊標執行 conn.commit() # 提交事務 except: conn.rollback() # 當SQL語句執行失敗時,回滾 finally: if cursor: cursor.close() # 關閉遊標 if conn: conn.close() # 關閉鏈接
用於執行SQL語句的兩個方法爲:
sql = r"select * from test.student" res = cursor.execute(sql)
executemany用於批量執行sql語句,
sql = r"insert into test.student (id,name,age) values (%s,'daxin',20)" cursor.executemany(sql,(7,8,9,))
咱們通常在程序中使用sql,可能會有以下代碼:
# 接受用戶id,而後拼接查詢用戶信息的SQL語句,而後查詢數據庫。 userid = 10 # 來源於程序 sql = 'select * from user where user_id = {}'.format(userid)
userid是可變的,好比經過客戶端發來的request請求,直接拼接到查詢字符串中。若是客戶端傳遞的userid是 '5 or 1='呢
sql = 'select * from test.student where id = {}'.format('5 or 1=1')
此時真正在數據庫中查詢的sql語句就變成了
select * from test.student where id = 5 or 1=1
這條語句的where條件的函數含義是:當id等於5,或者1等於1時,列出全部匹配的字段,這樣就輕鬆的查到了標準全部的數據!!!
永遠不要相信客戶端傳來的數據是規範及安全的。
cursor.execute(sql)方法還額外提供了一個args參數,用於進行參數化查詢,它的類型能夠爲元組、列表、字典。若是查詢字符串使用命名關鍵字(%(name)s),那麼就必須使用字典進行傳參。
sql = 'select * from test.student where id = %s' cursor.execute(sql,args=(2,)) sql = 'select * from test.student where id = %(id)s' cursor.execute(sql,args={'id':2})
咱們說使用參數化查詢,效率會高一點,爲何呢?由於SQL語句緩存。數據庫通常會對SQL語句編譯和緩存,編譯只對SQL語句部分,因此參數中就算有SQL指令也不會被執行。編譯過程,須要此法分析、語法分析、生成AST、優化、生成執行計劃等過程,比較耗費資源。服務端會先查找是不是同一條語句進行了緩存,若是緩存未失效,則不須要再次編譯,從而下降了編譯的成本,下降了內存消耗。
能夠認爲SQL語句字符串就是一個key,若是使用拼接方案,每次發過去的SQL語句都不同,都須要編譯並緩存。大量查詢的時候,首選使用參數化查詢,以節省資源。
Cursor類提供了三種查詢結果集的方法:(返回值爲元組,多個值爲嵌套元組)
fetchone()
:獲取一條fetchmany(size=None)
:獲取多條,當size爲None時,返回一個包含一個元素的嵌套元組fetchall()
:獲取全部sql = 'select * from test.student' cursor.execute(sql) print(cursor.fetchall()) print(cursor.fetchone()) # None print(cursor.fetchmany(2)) # None
fetch存在一個指針,fetchone一次,就讀取結果集中的一個結果,若是fetchall,那麼一次就會讀取完全部的結果。能夠經過調整這個指針來重複讀取
sql = 'select * from test.student' cursor.execute(sql) print(cursor.fetchall()) # ((2, 'dahl', 20), (3, 'dahl', 21), (4, 'daxin', 22), (5, 'dahl', 23), (6, 'dahl', 23), (7, 'daxin', 20), (8, 'daxin', 20), (9, 'daxin', 20)) cursor.rownumber = 0 print(cursor.fetchone()) # (2, 'dahl', 20) print(cursor.fetchmany()) # ((3, 'dahl', 21),)
fetch操做的是結果集,結果集是保存在客戶端的,也就是說fetch的時候,查詢已經結束了。
結果中不包含字段名,除非咱們記住字段的順序,否則很麻煩,那麼下面來解決這個問題。
觀察cursor原碼,咱們發現,它接受一個參數cursor,有一個參數是:DictCursor
,查看源碼得知,它是Cursor的Mixin子類。
def cursor(self, cursor=None): """ Create a new cursor to execute queries with. :param cursor: The type of cursor to create; one of :py:class:`Cursor`, :py:class:`SSCursor`, :py:class:`DictCursor`, or :py:class:`SSDictCursor`. None means use Cursor. """ if cursor: return cursor(self) return self.cursorclass(self)
觀察DictCursor的原碼,得知結果集會返回一個字典,一個字段名:值的結果,因此,只須要傳入cursor參數便可
from pymysql import cursors # DictCursor存在與cursors模塊中 ... ... cursor = conn.cursor(cursors.DictCursor) sql = 'select * from test.student' cursor.execute(sql) print(cursor.fetchone()) # {'id': 2, 'name': 'dahl', 'age': 20}
Connection和Cursor類實現了__enter__和__exit__方法,因此它支持上下文管理。
因此利用上下文的特性,咱們能夠這樣使用
import pymysql def connect_mysql(): db_config = { 'host': '10.0.0.13', 'port': 3306, 'user': 'dahl', 'password': '123456', 'charset': 'utf8' } return pymysql.connect(**db_config) if __name__ == '__main__': conn = connect_mysql() with conn as cursor: sql = 'select * from student' cursor.execute(sql) print(cursor.fetchmany(2)) # conn的exit只是提交了,並無關閉curosr cursor.close() conn.close()
若是要自動關閉cursor,能夠進行以下改寫
if __name__ == '__main__': conn = connect_mysql() with conn as cursor: with cursor # curosr的exit會關閉cursor sql = 'select * from student' cursor.execute(sql) print(cursor.fetchmany(2)) conn.close()
多個 cursor共享一個 conn
在python編程中可使用MySQLdb/pymysql等模塊對數據庫的鏈接及諸如查詢/插入/更新等操做,可是每次鏈接mysql數據庫請求時,都是獨立的去請求訪問,至關浪費資源,並且訪問數量達到必定數量時,對mysql的性能會產生較大的影響。所以,實際使用中,一般會使用數據庫的鏈接池技術,來訪問數據庫達到資源複用的目的。
鏈接池對性能的提高表如今:
DBUtils是一套Python數據庫鏈接池包,並容許對非線程安全的數據庫接口進行線程安全包裝。DBUtils來自Webware for Python。
DBUtils提供兩種外部接口:
PooledDB
:提供線程間可共享的數據庫鏈接,並自動管理鏈接。爲每一個線程建立一個,資源消耗太大,因此咱們經常使用的是PooledDB.
PooledDB經常使用的參數爲:
creator
=pymysql: 使用連接數據庫的模塊maxconnections
=6: 鏈接池容許的最大鏈接數,0和None表示不限制鏈接數mincached
=2: 初始化時,連接池中至少建立的空閒的連接,0表示不建立,若是空閒鏈接數小於這個數,pool會建立一個新的鏈接maxcached
=5: 連接池中最多閒置的連接,0和None不限制,若是空閒鏈接數大於這個數,pool會關閉空閒鏈接blocking
=True: 鏈接池中若是沒有可用鏈接後,是否阻塞等待。True,等待;False,不等待而後報錯0
= None = never4
= when a query is executed安裝DBUtils
pip install DBUtils
示例代碼:
import threading import time from DBUtils.PooledDB import PooledDB import pymysql POOL = PooledDB( creator=pymysql, maxconnections=6, mincached=2, maxcached=5, # maxshared=3, blocking=True, maxusage=None, setsession=[], ping=0, host='10.0.0.13', port=3306, user='dahl', password='123456', database='test', charset='utf8' ) def func(): conn = POOL.connection() cursor = conn.cursor(pymysql.cursors.DictCursor) time.sleep(2) cursor.execute('select * from employees') res = cursor.fetchall() conn.close() print(threading.get_ident(), res) if __name__ == '__main__': for i in range(10): threading.Thread(target=func).start()