pymysql(第三方庫):
首先,必須先和數據庫建立一個傳輸數據的連接通道,需要用到pymysql下的connect()方法
pymysql.connect() 方法返回的是Connections類下的Connection 實例,connect() 方法傳參就是在給Connection類的 _init_ 初始化不定長參數,也可以理解爲 connect() 方法就是在創建新的 Connetion 對象
connect() / Connection初始化 常用參數 | 說明 |
---|---|
host | 主機ip |
user | 用戶名 |
password | 密碼 |
database | 數據庫 |
port | 端口號 |
charset | 字符集 |
import pymysql # 建立傳輸數據的鏈接通道 conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8") print(conn) # <pymysql.connections.Connection object at 0x000001ADB3CD4E50>
在使用pymysql.connect() 方法與數據庫建立連接後,想要操作數據庫時,就需要使用遊標 Cursor
通過連接好的數據庫(此處爲conn)調用 cursor() 方法即可返回一個新的遊標對象,在連接沒有關閉之前,遊標對象可以反覆使用
# 獲取遊標,用於操作數據庫 cursor = conn.cursor() print(cursor) # <pymysql.cursors.Cursor object at 0x000001ADB3ED4FD0>
常用基礎方法:
execute():發送指令
fetchone():取出下一條記錄
fetchchall():取出所有數據
commit():提交變化到穩定存儲(在對數據庫進行增刪改的時候需要做的操作)
import pymysql # 建立傳輸數據的鏈接通道 conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8") print(conn) # <pymysql.connections.Connection object at 0x000001C3C6687FD0> # 獲取遊標,用於操作數據庫 cursor = conn.cursor() print(cursor) # <pymysql.cursors.Cursor object at 0x000001C3C6687F70> # 執行一個查詢(即:發送指令) cursor.execute("show databases") # 取出下一條記錄(有點像可迭代對象(iterable),具有next方法,可以逐一獲取每行數據,但不是一個可迭代對象) result = cursor.fetchone() print(result) # ('db1',) # 取出所有行,由於上面代碼去了一條記錄,所以fetchall獲取的數據沒有上面的那條數據 result = cursor.fetchall() print(result) # (('information_schema',), ('mysql',), ('performance_schema',), ('sys',)) # 創建數據庫 cursor.execute("create database db3 default charset utf8 collate utf8_general_ci") # 增、刪、改 都需要使用commit (提交變化到穩定存儲)。 conn.commit() cursor.execute("show databases") print(cursor.fetchall()) # (('db1',), ('db3',), ('information_schema',), ('mysql',), ('performance_schema',), ('sys',)) # 刪除數據庫 cursor.execute("drop database db3") conn.commit() cursor.execute("show databases") print(cursor.fetchall()) # (('db1',), ('information_schema',), ('mysql',), ('performance_schema',), ('sys',)) cursor.execute("use db1") print(cursor.fetchall()) # () cursor.execute("show tables") result = cursor.fetchall() print(result) # (('123',), ('emp',), ('employee',), ('student',), ('student_1',))
import pymysql # 輸入用戶名和密碼 user = input("請輸入用戶名:") # ' or 1=1 -- pwd = input("請輸入密碼:") # 123 conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8",db='usersdb') cursor = conn.cursor() # 基於字符串格式化來 拼接SQL語句 # sql = "select * from users where name='root' and password='123'" # sql = "select * from users where name='' or 1=1 -- ' and password='123'" sql = "select * from users where name='{}' and password='{}'".format(user, pwd) cursor.execute(sql) result = cursor.fetchone() print(result) # None,不是None cursor.close() conn.close()
爲什麼呢?
因爲在SQL拼接時,拼接後的結果是:
select * from users where name='' or 1=1 -- ' and password='123'
注意:在MySQL中 --
表示註釋。
切記,SQL語句不要在使用python的字符串格式化,而是使用pymysql的execute方法。
# SQL語句不在用python的字符串格式化,而是使用pymsql的execute方法 import pymysql # 輸入賬號密碼 user = input("輸入賬號") pw = input("輸入密碼") # 創建數據庫鏈接 conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8") # 創建遊標 cursor = conn.cursor() # 方式一:發送指令,execute會自動幫助我們檢測,user、pw中是否存在特殊字符並幫助處理。 cursor.execute("select * from user where name=%s and password=%s", [user, pw]) # 方式二: # cursor.execute("select * from user where name=%(name)s and password=%(passwd)s", {"name": user, "passwd": pw}) result = cursor.fetchall() print(result) # 關閉遊標、關閉鏈接 cursor.close() conn.close()
什麼是數據庫事務
數據庫事務是訪問並可能操作各種數據項的一個數據庫操作(包括增刪改,不包括查詢)序列,這些操作要麼全部執行,要麼全部不執行,是一個不可分割的工作單位。事務由事務開始與事務結束之間執行的全部數據庫操作組成。
import pymysql # 創建鏈接 conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8") # 這樣寫可以直接進入到數據庫shool中 # conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8", db="shool") # 創建遊標 cursor = conn.cursor() # 進入數據庫 cursor.execute("use shool") # 開啓事務 conn.begin() # 嘗試執行事務 try: cursor.execute("update student set sname=%s where sid=8", ["某人飛"]) except Exception as e: print("發生異常,進行回滾") conn.rollback() else: print("提交事務") conn.commit() # 關閉遊標、關閉鏈接 cursor.close() conn.close()
鎖
在用MySQL時,不知你是否會疑問:同時有很多做更新、插入、刪除動作,MySQL如何保證數據不出錯呢?
MySQL中自帶了鎖的功能,可以幫助我們實現開發過程中遇到的同時處理數據的情況。對於數據庫中的鎖,從鎖的範圍來講有:
-
表級鎖,即A操作表時,其他人對整個表都不能操作,等待A操作完之後,才能繼續。
-
行級鎖,即A操作表時,其他人對指定的行數據不能操作,其他行可以操作,等待A操作完之後,才能繼續。
MYISAM支持表鎖,不支持行鎖;
InnoDB引擎支持行鎖和表鎖。
即:在MYISAM下如果要加鎖,無論怎麼加都會是表鎖。
在InnoDB引擎支持下如果是基於索引查詢的數據則是行級鎖,否則就是表鎖。
在innodb引擎中,update、insert、delete的行爲內部都會先申請鎖(排它鎖),申請到之後才執行相關操作,最後再釋放鎖。
所以,當多個人同時像數據庫執行:insert、update、delete等操作時,內部加鎖後會排隊逐一執行。
而select則默認不會申請鎖 select * from xxx;
如果,你想要讓select去申請鎖,則需要配合 事務 + 特殊語法來實現。
- for update,排它鎖,加鎖之後,其他不可以讀寫。
begin; select * from L1 where name="飛某人" for update; -- name列不是索引(表鎖) commit;
begin; -- 或者 start transaction; select * from L1 where id=1 for update; -- id列是索引(行鎖) commit;
當行鎖是id(索引)時,其他事務也需要有單獨的索引纔不會被阻塞,否則數據會通過id列就導致了阻塞
- lock in share mode,共享鎖,加鎖之後,其他可讀但不可寫。
begin; select * from L1 where name="飛某人" lock in share mode; -- 假設name列不是索引(表鎖) commit;
begin; -- 或者 start transaction; select * from L1 where id=1 lock in share mode; -- id列是索引(行鎖) commit;
排它鎖
排它鎖( for update),加鎖之後,其他事務不可以讀寫。
應用場景:總共100件商品,每次購買一件需要讓商品個數減1 。
A: 訪問頁面查看商品剩餘 100 B: 訪問頁面查看商品剩餘 100 此時 A、B 同時下單,那麼他們同時執行SQL: update goods set count=count-1 where id=3 由於Innodb引擎內部會加鎖,所以他們兩個即使同一時刻執行,內部也會排序逐步執行。 但是,當商品剩餘 1個時,就需要注意了。 A: 訪問頁面查看商品剩餘 1 B: 訪問頁面查看商品剩餘 1 此時 A、B 同時下單,那麼他們同時執行SQL: update goods set count=count-1 where id=3 這樣剩餘數量就會出現 -1,很顯然這是不正確的,所以應該怎麼辦呢? 這種情況下,可以利用 排它鎖,在更新之前先查詢剩餘數量,只有數量 >0 纔可以購買,所以,下單時應該執行: begin; -- start transaction; select count from goods where id=3 for update; -- 獲取個數進行判斷 if 個數>0: update goods set count=count-1 where id=3; else: -- 已售罄 commit;
基於Python代碼示例:
import pymysql import threading def task(): # 創建鏈接 conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8", db="db1") # 創建遊標 # 參數的作用是將遊標接收到的數據整理成字典的形式 cursor = conn.cursor(pymysql.cursors.DictCursor) # 開啓事務 conn.begin() # 嘗試執行事務 try: cursor.execute("select id,age from tran where id=1 for update") # fetchall 有參:( {"id":1,"age":10},{"id":2,"age":10}, ) 無參獲取的結果:((1,10),(2,10)) # fetchone 有參{"id":1,"age":10} 無參獲取的結果:(1,10) result = cursor.fetchone() print(result) current_age = result['age'] if current_age > 0: cursor.execute("update tran set age=age-1 where id=1") else: print("已售罄") except Exception as e: print("異常,進行回滾") conn.rollback() else: print("提交事務") conn.commit() cursor.close() conn.close() def fun(): thread = threading.Thread(target=task) thread.start() if __name__ == '__main__': for x in range(11): fun()
假設在用戶使用時,每個用戶都會獲取一個數據庫連接,如果有很多用戶同時申請獲取連接,然後使用後連接也都斷開了,如果又要連接......這樣的過程很明顯用上面的代碼是行不通的,數據庫頂不住啊!
這時候就需要一個數據庫連接池,來管理用戶對數據庫連接的操作。
在操作數據庫時需要使用數據庫連接池
pip3.9 install pymysql
pip3.9 install dbutils
# 導入連接池工具包 import threading import pymysql from dbutils.pooled_db import PooledDB MYSQL_DB_POOL = PooledDB( creator=pymysql, # 使用鏈接數據庫的模塊,因爲dbutils並不能鏈接數據庫,真正連接數據庫還是需要pymsql maxconnections=5, # 連接池允許的最大連接數,0和None表示不限制連接數 mincached=2, # 初始化時,鏈接池中至少創建的空閒的鏈接,0表示不創建 maxcached=3, # 鏈接池中最多閒置的鏈接,0和None不限制 blocking=True, # 連接池中如果沒有可用連接後,是否阻塞等待。True,等待;False,不等待然後報錯 setsession=[], # 開始會話前執行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服務端,檢查是否服務可用。 # 如:0 = None = never, 1 = default = whenever it is requested, # 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='root', database='shool', charset='utf8' ) def task(): # 獲取連接池中的連接 conn = MYSQL_DB_POOL.connection() # 獲取連接中的遊標 cursor = conn.cursor(pymysql.cursors.DictCursor) # cursor = conn.cursor() # 執行命令 cursor.execute("show tables") # 接受數據 result = cursor.fetchall() print(result) cursor.close() # 把鏈接交還給數據庫連接池 conn.close() def run(): for x in range(10): thread = threading.Thread(target=task) thread.start() if __name__ == '__main__': run()
因爲數據庫連接池只有一個,所以我們需要基於數據庫連接池開發一個公共的SQL操作,方便以後操作數據庫。
# 數據庫連接池_單例.py import pymysql from dbutils.pooled_db import PooledDB # 模塊的方式實現單例 class DBHelper(object): # 初始化數據庫連接池 def __init__(self): # TODO 此處配置,可以去配置文件中讀取。 self.pool = PooledDB( creator=pymysql, # 使用鏈接數據庫的模塊 maxconnections=5, # 連接池允許的最大連接數,0和None表示不限制連接數 mincached=2, # 初始化時,鏈接池中至少創建的空閒的鏈接,0表示不創建 maxcached=3, # 鏈接池中最多閒置的鏈接,0和None不限制 blocking=True, # 連接池中如果沒有可用連接後,是否阻塞等待。True,等待;False,不等待然後報錯 setsession=[], # 開始會話前執行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服務端,檢查是否服務可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='root', database='shool', charset='utf8' ) # 獲取連接和遊標 def get_conn_cursor(self): conn = self.pool.connection() cursor = conn.cursor(pymysql.cursors.DictCursor) return conn, cursor # 關閉遊標,把鏈接交還給連接池 def close_conn_cursor(self, *args): for item in args: item.close() # 發送指令 def exec(self, sql, **kwargs): conn, cursor = self.get_conn_cursor() cursor.execute(sql, kwargs) conn.commit() self.close_conn_cursor(conn, cursor) # 獲取一條數據 def fetch_one(self, sql, **kwargs): conn, cursor = self.get_conn_cursor() cursor.execute(sql, kwargs) result = cursor.fetchone() self.close_conn_cursor(conn, cursor) return result # 獲取所有數據 def fetch_all(self, sql, **kwargs): conn, cursor = self.get_conn_cursor() cursor.execute(sql, kwargs) result = cursor.fetchall() self.close_conn_cursor(conn, cursor) return result db = DBHelper()
# 數據庫連接池測試.py # 測試模塊的方式實現的單例 from 數據庫連接池_單例 import db db.exec("insert into student(sname) values(%(name)s)", name="飛某人") ret = db.fetch_one("select * from student") print(ret) ret = db.fetch_one("select * from student where sid=%(nid)s", nid=3) print(ret) ret = db.fetch_all("select * from student") print(ret) ret = db.fetch_all("select * from student where sid>%(nid)s", nid=2) print(ret)
with 獲取連接:
執行SQL(執行完畢後,自動將連接交還給連接池)
# 數據庫連接池_單例.py import pymysql from dbutils.pooled_db import PooledDB # 上下文管理方式實現單例 # 創建一個連接池 POOL = PooledDB( creator=pymysql, # 使用鏈接數據庫的模塊,因爲dbutils並不能鏈接數據庫,真正連接數據庫還是需要pymsql maxconnections=5, # 連接池允許的最大連接數,0和None表示不限制連接數 mincached=2, # 初始化時,鏈接池中至少創建的空閒的鏈接,0表示不創建 maxcached=3, # 鏈接池中最多閒置的鏈接,0和None不限制 blocking=True, # 連接池中如果沒有可用連接後,是否阻塞等待。True,等待;False,不等待然後報錯 setsession=[], # 開始會話前執行的命令列表。如:["set datestyle to ...", "set time zone ..."] ping=0, # ping MySQL服務端,檢查是否服務可用。 # 如:0 = None = never, 1 = default = whenever it is requested, # 2 = when a cursor is created, 4 = when a query is executed, 7 = always host='127.0.0.1', port=3306, user='root', password='root', database='shool', charset='utf8' ) class Connect(object): # 初始化數據庫連接與遊標 def __init__(self): self.conn = POOL.connection() self.cursor = self.conn.cursor(pymysql.cursors.DictCursor) # 上文管理 def __enter__(self): return self # 下文管理 def __exit__(self, exc_type, exc_val, exc_tb): self.cursor.close() self.conn.close() # 發送指令 def exec(self, sql, **kwargs): self.cursor.execute(sql, kwargs) self.conn.commit() # 獲取一條數據 def fetch_one(self): result = self.cursor.fetchone() return result # 獲取所有數據 def fetch_all(self): result = self.cursor.fetchall() return result
# 數據庫連接池測試.py # 上下文管理測試 from 數據庫連接池_單例 import Connect with Connect() as obj: obj.exec("select * from student") result = obj.fetch_one() print(result) result = obj.fetch_all() print(result) obj.exec("select * from student where sname=%(name)s or sid=%(tid)s", name="某人飛", tid=4) result = obj.fetch_all() print(result)