遊標mysql
在stored Routines調用中開的一個緩衝區,用於存放SQL調用的結果集。(結果集是隻讀的)sql
意味着咱們的查詢能夠返回一個文檔也能夠返回一個遊標去指向一個結果集,然後經過遊標的切換而獲取每一個結果數據庫
Python鏈接數據庫緩存
涉及模塊安全
mysqldb py3再也不更新多線程
pymysql py3用的模塊異步
安裝pymysql 模塊socket
pip install pymysqlide
導入模塊函數
import pymysql
查看pymysql源碼
默認自動提交是False的
找到 Connection 類中,看以下代碼:
def __init__(self, host=None, user=None, password="",
database=None, port=0, unix_socket=None,
charset='', sql_mode=None,
read_default_file=None, conv=None, use_unicode=None,
client_flag=0, cursorclass=Cursor, init_command=None,
connect_timeout=10, ssl=None, read_default_group=None,
compress=None, named_pipe=None, no_delay=None,
autocommit=False, db=None, passwd=None, local_infile=False,
max_allowed_packet=16*1024*1024, defer_connect=False,
auth_plugin_map={}, read_timeout=None, write_timeout=None,
bind_address=None, binary_prefix=False
):
鏈接數據庫,判斷數據庫正常與否
使用try或者使用ping方法均可以
經過ping測試連通性
經過判斷是不是None或者異常,則認爲服務是不是存活的
import pymysql
conn = pymysql.connect('ip','root','123456')
print(conn.ping)
print(conn.ping(False))
若是鏈接失敗咱們要對其進行關閉,因此最好加入到try中
import pymysql
try:
conn = pymysql.connect('x.x5.x','root','123456','test110')
print(conn.ping(False))
finally:
if conn:
conn.close()
遊標 Curosr
在stored Routines調用中開的一個緩衝區,用於存放SQL調用的結果集。(結果集是隻讀的)
意味着咱們的查詢能夠返回一個文檔也能夠返回一個遊標去指向一個結果集,然後經過遊標的切換而獲取每一個結果
在操做數據庫的時候必須使用cursor類的實例,提供execute()方法,執行sql語句返回成功的行數
執行sql語句
import pymysql
try:
conn = pymysql.connect('.x4.1.1','root','123456','test110')
print(conn.ping(False))
cursor = conn.cursor() # 獲取遊標
insert_sql = "insert into student(name,age) values('jerry',20)"
line = cursor.execute(insert_sql) # 執行
print(line)
finally:
if conn:
conn.close()
返回以下:
None
1
可是數據庫中並沒有數據,由於沒有commit
在Connection類中,默認設置的是
通常不須要開啓自動提交,須要手動管理事務並統一提交
事物的管理
有任何異常conn都要回滾確保數據無誤,若是沒有異常則commit()
close和commit 沒有前後關係順序
一個標準的數據庫鏈接和操做關閉流程
import pymysql
conn = None
try:
conn = pymysql.connect('47.xxx','root','123456','test110')
print(conn.ping(False))
# 獲取遊標
cursor = conn.cursor()
insert_sql = "insert into student(name,age) values('jerry',20)"
line = cursor.execute(insert_sql)
cursor.close()
conn.commit()
except:
conn.rollback()
finally:
if conn:
conn.close()
批量插入
try:
conn = pymysql.connect('111.11.110','root','123456','test110')
print(conn.ping(False))
# 獲取遊標
cursor = conn.cursor()
#插入數據
for i in range(10):
insert_sql = "insert into student(name,age) values('jerry',{})".format(i)
line = cursor.execute(insert_sql)
print('line:',line)
cursor.close()
conn.commit()
每每會將批量的修改所有寫前面,最後統一執行commit,遊標關閉和連接提交是能夠不分前後的
操做流程
創建鏈接--> 獲取遊標 --> 執行SQL --> 提交事物 --> 釋放資源
若是出現異常須要回滾再釋放資源
查詢
不須要事物的地方必定不要使用事物,影響效率
查看分別有什麼區別
conn = pymysql.connect('47.4.xx.xx','root','123456','test110')
print(conn.ping(False))
# 獲取遊標
cursor = conn.cursor()
sql = 'select * from student'
line = cursor.execute(sql)
#
獲取前兩個
# print(cursor.fetchaone())
# print(cursor.fetchall())
獲取5個並以元組方式返回
print(cursor.fetchmany(5))
print(cursor.fetchmany(5))
獲取剩下的所有
print(cursor.fetchall())
查看返回結果fethchall之因此獲取不多的數據,由於都使用的是遊標,相似於一個指針
重置遊標
print(cursor.fetchmany(5))
print(cursor.fetchall())
cursor.rownumber = 0
print(cursor.fetchall())
這樣指針又指向了0,實際上是指向的索引
查看fethchall源碼
實際上就是作了一個切片,以切片方式並記錄當前位置返回咱們想要的結果
def fetchall(self):
"""Fetch all the rows"""
self._check_executed()
if self._rows is None:
return ()
if self.rownumber:
result = self._rows[self.rownumber:]
else:
result = self._rows
self.rownumber = len(self._rows)
return result
DictCursor 字典遊標,帶一些字段名方式進行返回
Cursor 類有一個Mixin的子類 DictCursor
導入模塊
from pymysql.cursors import DictCursor
cursor = conn.cursor(cursor=DictCursor)
以上是元組和字典的返回差別
返回以下:
[{'name': 'jerry', 'en': None, 'age': 1}, {'name': 'jerry', 'en': None, 'age': 2}]
SQL注入***
在登錄的時候作了一些匹配或者明文匹配所致使
通常狀況都須要進行加密
好比這樣的語句:
select * from t where name='ben' and password='ben';
在登錄時要作惟一約束的,用戶在寫提交程序的時候,用戶名須要異步去驗證
這個過程已是查過數據庫了
在登錄時要作惟一約束的,用戶在寫提交程序的時候,用戶名須要異步去驗證
這個過程已是查過數據庫了
可是用戶在執行的時候加了這麼一句
select * from t where name='ben' and password='ben' or 1 = 1 ;
or 1=1 是真值,至關於 select *
正常字符串拼接所形成的
正常的拼接:
name = 'jerry'
age = '3 or 1=1'
sql = 'select * from student where name={} and age={}'.format(name,age)
這樣是最原始的拼接字符串的方式,太危險
這樣經過id 或者其餘來獲取你的數據,總有一個參數能返回數據,每一個參數都會遭到***
SELECT * FROM t where a = 1 and b = 1 or 1 = 1 and id=5 or 1=1;
若是密碼失敗,那麼還能夠經過查詢來進行***,這樣也會返回數據
因此,不能使用字符串拼接的方式來拼寫sql
凡是用戶提交的數據都不可信,要作嚴格的檢查,哪怕是調用函數
解決sql注入
參數化查詢,能夠有效防止注入***,並提升查詢效率
經過cursor.execute參數進行防注入
cursor.execute(query,args=None)
查看args源碼
def execute(self, query, args=None):
"""Execute a query
:param str query: Query to execute.
:param args: parameters used with query. (optional)
:type args: tuple, list or dict 明確寫明args必須是一個元組列表或者字典
:return: Number of affected rows
:rtype: int
If args is a list or tuple, %s can be used as a placeholder in the query.
If args is a dict, %(name)s can be used as a placeholder in the query.
"""
while self.nextset():
pass
query = self.mogrify(query, args)
result = self._query(query)
self._executed = query
return result
經過拼接字符串的方式是能看到別人全部數據,那麼改進以下:
try:
conn = pymysql.connect('4x.x.x.x','root','123456','test110')
cursor = conn.cursor(DictCursor)
age = '20 or 1=1'
sql = 'SELECT * FROM student where age=%s'
# sql =' SELECT * FROM student where age={}'.format(age)
cursor.execute(sql,(age,)) #將規則寫入到execute中,判斷是否有其餘敏感字符
print(cursor.fetchall())
Warning: (1292, "Truncated incorrect DOUBLE value: '3 or 1=1'")
[{'name': 'jerry', 'age': 3, 'en': None}]
self._do_get_result()
儘量轉爲目標的數據,友好的作了轉換,可是發現有一些非法的字段被攔截掉了
提示非法DOUBLE的類型
更加複雜的例子
sql = 'SELECT * FROM student where age > %(age)s'
# sql =' SELECT * FROM student where age={}'.format(age)
cursor.execute(sql,{'age':10}) #參數化查詢
print(cursor.fetchall())
參數化爲啥能夠提升效率?
由於sql語句緩存
客戶端每發一次sql語句到mysql中都有一個sql編譯過程,只是對sql語句進行編譯
若是不用參數化查詢,那麼id=1 id=2 id=3 來了三條語句,須要從新編譯
mysql服務端會對sql語句編譯和緩存,編譯只對sql部分,只是sql部分並非結果
編譯過程,須要語法分析,生成AST並優化生成執行計劃等過程 這個過程比較耗費資源
可認爲自己sql語句字符串就是一個key,找到key則直接找到結果
那麼若是使用拼接的方案,每次發過去的sql都不同,都須要編譯並緩存
開發時,應該使用參數化查詢
這裏只是查詢的字符串的緩存,並非查詢結果
遊標的上下文
查看遊標的源碼 __enter__ 和 __exit__
遊標類:
def __enter__(self):
return self #返回本身
def __exit__(self, *exc_info):
del exc_info
self.close() #調用close()關閉本身
鏈接類進入上下文的時候會返回一個遊標對象,就是遊標本身
遊標類也使用上下文,用完了以後還會調用enter 和 exit
在退出時關閉遊標對象,執行 self.close()
查看close源碼:
def close(self):
"""
Closing a cursor just exhausts all remaining data.
"""
conn = self.connection
if conn is None:
return
try:
while self.nextset():
pass
finally:
self.connection = None
def __enter__(self):
return self
def __exit__(self, *exc_info):
del exc_info
self.close()
建立的時候用的是cursor,鏈接的時候也能夠關閉
最後將鏈接 = None,說明沒有鏈接,沒法使用
鏈接類的上下文
有沒有with as xxx ,是定義的問題,在退出with的時候,查看有否異常,若是存在異常則回滾
進入鏈接類的時候會返回一個遊標
鏈接類:
def __enter__(self):
"""Context manager that returns a Cursor"""
return self.cursor()
def __exit__(self, exc, value, traceback):
"""On successful exit, commit. On exception, rollback"""
if exc:
self.rollback()
else:
self.commit() #若是沒有異常則提交
建立的時候用的是self.cursor(),經過enter 進來的時候調用self.cursor(),直接調用了遊標
遊標經過調用本地方法獲取
def cursor(self, cursor=None):
"""Create a new cursor to execute queries with"""
if cursor:
return cursor(self)
return self.cursorclass(self)
若是沒有存在,那麼直接調用cursorclass ,那麼cursorclass直接調用遊標類
而cursorclass 就是在Connection 初始化中去獲取
self.cursorclass = cursorclass
cursorclass直接指向了遊標類,經過調用遊標類返回一個本身的實例提供調用
總結
鏈接:
遊標的上下文是返回本身提供使用的,在close()將遊標關閉,關閉的是本身將其標記爲None
對於鏈接來說,在with進入以後返回的是cursor()遊標本身的對象
鏈接類以下,在調用它的時候,上下文先執行,並將遊標類調用,因此調用的是cursor()
def __enter__(self):
"""Context manager that returns a Cursor"""
return self.cursor()
關鍵是關閉的時候並無自行關閉鏈接,由於鏈接是共用鏈接(長鏈接),因此不會關閉鏈接的,可是遊標須要關閉,徹底由用戶控制
退出:
可是若是退出with語句塊,確定會檢查是否有異常,提交或者回滾
當離開語句塊的時候會提交或回滾
因此,代碼須要以下改進:
import pymysql
from pymysql.cursors import DictCursor
conn = None
try:
# 創建鏈接
conn = pymysql.connect('7.94.xx','root','123456','test110')
# cursor = conn.cursor(DictCursor) #註釋遊標獲取,在with中已經獲取了遊標
with conn as cursor: #代替了上一上,在進入上下文的時候,conn已經獲取了遊標
d = {'age':'5'}
sql = 'select * from student where age>%(age)s'
print(sql)
# 執行
line = cursor.execute(sql,d)
print(line)
print(cursor.fetchall())
except:
print('errrrrr')
finally:
if conn:
conn.close()
這樣進入到with 鏈接對象 as cursor ,的時候直接調用了conn鏈接對象的上下文,並調用了遊標類
返回了遊標的self,這樣就能夠直接在with中調用,免去了開銷
若是使用遊標的上下文則能夠:
conn = None
try:
# 創建鏈接
conn = pymysql.connect('ip','root','123456','test110')
# cursor = conn.cursor(DictCursor)
with conn as cursor:
with cursor:
d = {'age':10}
sql = 'select * from student where age<%(age)s'
cursor.execute(sql,d)
print(cursor.fetchall())
except:
print('errrrrr')
finally:
if conn:
conn.close()
當with conn as的時候, 返回一個新的cursor對象,當退出時,只要提交或者回滾了事物,並無關閉
不關閉遊標表示能夠繼續反覆使用它,節省了開銷
可是在最後finally中定義了關閉
finally:
if conn:
conn.close()
鏈接池
數據庫最大的開銷實際上是鏈接,因此引入了鏈接池的概念
設置一個數據庫鏈接池,使用者若是須要則get一個
分析
一個鏈接池,應該是一個能夠設置大小容器,存放數據庫的鏈接,使用者須要鏈接從池中獲取一個鏈接,用完歸還
啓動的時候開啓鏈接到數據庫中,鏈接數據庫的時候避免了頻繁鏈接數據庫,也限制了主動鏈接
鏈接池中只是存放的鏈接,具體如何使用是用戶的事情,只須要存放一個正常的鏈接便可
選型
對於線程、所機制、信號量等無非使用Queue比較合適當前情景
Queue自己就保證了線程的絕對安全性
# coding:utf-8
import pymysql
from pymysql.cursors import DictCursor
from queue import Queue
conn = None
class ConnPool:
def __init__(self,size,*args,**kwargs):
self.size = size
self._pool = Queue
for i in range(size):
conn = pymysql.connect(*args,**kwargs) # 傳入用戶名密碼
self._pool.put(conn) # 傳入到隊列,生產者
def get_conn(self):
return self._pool.get()
def return_conn(self,conn:pymysql.connections.Connection):
self._pool.get(conn) # 消費鏈接
線程池引入:初始化線程池、初始化以後上面代碼就等於有了鏈接了
接下來就是如何使用的問題
那麼能否將id放入到集合裏,集合中是不容許重複的,id也是
判斷只要地址不一樣就能夠,那麼也有風險,若是內存被回收,或者在這時間又賦予這個id內存地址
因此是有風險的,也不排除用戶的操做問題;
因此須要作類型的判斷;
判斷類型
import pymysql
from pymysql.cursors import DictCursor
from queue import Queue
conn = None
class ConnPool:
def __init__(self,size,*args,**kwargs):
self.size = size
self._pool = Queue(size)
for i in range(size):
conn = pymysql.connect(*args,**kwargs) # 傳入用戶名密碼
self._pool.put(conn) # 傳入到隊列,生產者
def get_conn(self):
return self._pool.get()
# 判斷類型是不是鏈接
def return_conn(self,conn:pymysql.connections.Connection):
if isinstance(conn,pymysql.connections.Connection):
self._pool.put(conn)
# 初始化類
pool = ConnPool(5,'4ip','root','123456','test')
#
conn = pool.get_conn()
print(conn)
# 消費鏈接
pool.return_conn(conn)
那麼對於沒有關閉的鏈接是須要手動的,因此可否在類中實現關閉,後期能夠在當前類的上下文
引入Thread.Local ,前提是隻有一個在用它,並且Thread.Local是順序執行
在每一個線程中用,限定在多線程的場景下使用,起碼保證線程內,Thread.Local是安全的
import pymysql
from pymysql.cursors import DictCursor
from queue import Queue
import threading
class ConnPool:
def __init__(self,size,*args,**kwargs):
self.size = size
self._pool = Queue(size)
self.local = threading.local()
for i in range(size):
conn = pymysql.connect(*args,**kwargs) # 傳入用戶名密碼
self._pool.put(conn) # 傳入到隊列,生產者
# 標記self.local.conn 獲取的時候則賦值並返回get到的鏈接
def get_conn(self):
# return self._pool.get()
conn = self._pool.get()
self.local.conn = conn # 標記當前鏈接,用於獲取put以後標記None,若是get到了則標記當前鏈接
return conn
# 線程是順序的,就是說一直用完到還回線程
def return_conn(self,conn:pymysql.connections.Connection):
if isinstance(conn,pymysql.connections.Connection):
self._pool.put(conn)
self.local.conn = None # 用完以後標記爲None,仿照鏈接類去寫
# 初始化類
pool = ConnPool(5,'47.9.x.x.','root','123456','test')
#
conn = pool.get_conn()
print(conn)
# 消費鏈接
pool.return_conn(conn)
上下文改進
遊標的上下文帶來了一些列問題,那麼能否自行增長上下文,若是是None則返回一個遊標
若是不是None,那麼就是鏈接了
enter 用因而否是None,是的話則賦予一個鏈接
exit 只要有一個退出,那麼就標記當前爲None
因此thread.local 仍是能夠的,由於都是在本地線程中使用,內存地址沒有改動,直到關閉
# 線程是順序的,就是說一直用完到還回線程
def return_conn(self,conn:pymysql.connections.Connection):
if isinstance(conn,pymysql.connections.Connection):
self._pool.put(conn)
self.local.conn = None # 用完以後標記爲None,仿照鏈接類去寫
def __enter__(self):
#剛進來的時候線程不存在則拋異常,確定是None,因此給一個鏈接
if getattr(self.local,'conn',None) is None:
self.local.conn = self.get_conn()
return self.local.conn.cursor() #返回一個遊標
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
self.local.conn.rollback()
else:
self.local.conn.commit()
self.return_conn(self.local.conn)
self.local.conn = None
# 初始化類
pool = ConnPool(5,'.x.x.x.x','root','123456','test')
with pool as cursor:
with cursor:
sql = 'select * from student'
cursor.execute(sql)
# print(cursor.fetchall())
for x in cursor:
print(x)