python用於操作數據的pymysql模塊,及相關的視圖,觸發器,函數,存儲過程,事務,SQL註入等相關拓展。 ...
內容來自本人以前在github搭建的博客寫的:MySQL高級之視圖事務函數觸發器
pymysql安裝
pymysql是python用於連接並操作資料庫的一個原生模塊
linux下:
pip3 install pymysql
sql註入
1.簡單模擬登錄
先來看一個簡單例子,代碼如下:
import pymysql user = input("username:") pwd = input("password:") # 與資料庫建立連接 conn = pymysql.connect(host="localhost", user="root", password="root", database="test") # 設置游標 cursor = conn.cursor() # 將要執行的sql語句 sql = "select * from userinfo where username = '%s' and password = '%s'" % (user, pwd) # 執行語句 cursor.execute(sql) # 用fetchone獲取查詢結果 result = cursor.fetchone() # 關閉連接 cursor.close() conn.close() if result: print("登錄成功") else: print("用戶名或密碼錯誤")
2.sql註入漏洞
註意,上面的代碼雖然可正確執行,但是是存在漏洞的。
如圖,漏洞為即使你隨便輸入一個賬戶,按照”xxx’ or 1=1 – “的格式,不輸入密碼也能登錄進去,這種漏洞我們稱呼為”sql註入”;問題主要存在於sql語句的寫法。
3.sql註入原理
如圖
4.避免sql註入
為了避免sql註入,我們不要自己做拼接,用pymysql自帶的execute後面傳參數的方式,有如下三種方法:
sql = "select * from userinfo where username = %s and password = %s" cursor.execute(sql, (user, pwd))
或者:
sql = "select * from userinfo where username = %s and password = %s" cursor.execute(sql, [user, pwd])
或者:
sql = "select * from userinfo where username = %(u)s and password = %(p)s" cursor.execute(sql, {'u': user, 'p': pwd})
順利解決sql註入問題,如圖
5.模擬登陸代碼
import pymysql name = input("username:") pwd = input("password:") conn = pymysql.connect(host='localhost', user='root', password='root', database='test') cursor = conn.cursor() sql = "select * from userinfo where name=%s and password=%s" r = cursor.execute(sql, (name, pwd)) # 方法二 # sql = "select * from userinfo where name=%s and password=%s" # r = cursor.execute(sql, [name, pwd]) # 方法三 # sql = "select * from userinfo where name=%(u)s and password=%(p)s" # r = cursor.execute(sql, {'u': name, 'p': pwd}) result = cursor.fetchone() cursor.close() conn.close() if result: print("登陸成功") else: print("賬戶名或密碼錯誤")
pymysql操作資料庫
1.pymysql增
1.1.插入一行數據
代碼如下:
import pymysql conn = pymysql.connect(host="localhost", user="root", password="root", database="test") cursor = conn.cursor() sql = "insert into userinfo(username,password) values('那英','naying')" cursor.execute(sql) conn.commit() cursor.close() conn.close()
值得註意的是,在增刪改時我們用的是commit進行提交,在查時用的是fetchone進行查找。
當然,我們也可以自定義插入,如下:
sql = "insert into userinfo(username,password) values(%s,%s)" cursor.execute(sql, [user, pwd]) conn.commit()
1.2.插入多行數據
在插入當行數據是,應註意此時我們用的是executemany,而不是execute。
sql = "insert into userinfo(username,password) values(%s,%s)" r = cursor.executemany(sql, [('張學友', 'zhangxueyou'), ('林俊傑', 'linjunjie')]) conn.commit()
1.3.execute的返回值r
execute和executemany都有返回值,用來記錄受影響的行數,如下:
sql = "insert into userinfo(username,password) values(%s,%s)" r = cursor.executemany(sql, [('張學友', 'zhangxueyou'), ('林俊傑', 'linjunjie')]) print(r) conn.commit()
結果為2;
不僅僅是增刪改會有受影響的行數,查詢時也會有。
同時,executemany只適用於插入數據,刪和改用execute即可。
2.pymysql查
2.1.fetchone
代碼如下:
result = cursor.fetchone() print(result) result = cursor.fetchone() print(result) result = cursor.fetchone() print(result)
結果:
(1, '劉德華', 'liudehua') (3, '郭德綱', 'guodegang') (4, '胡歌', 'huge')
fetchone可以單次查詢,也可以連續單次查詢,連續單次查詢時,會有類似於指針一樣的東西,當你查詢一次後,指針自動跳往下一行數據。
2.2.fetchall
fetchall在未指定limit時,預設查詢所有數據
代碼如下:
sql = "select * from userinfo" cursor.execute(sql) result = cursor.fetchall() print(result)
結果:
((1, ‘劉德華’, ‘liudehua’), (3, ‘郭德綱’, ‘guodegang’), (4, ‘胡歌’, ‘huge’), (5, ‘周傑倫’, ‘zhoujielun’), (7, ‘那英’, ‘naying’), (8, ‘王菲’, ‘wangfei’), (11, ‘張學友’, ‘zhangxueyou’), (14, ‘林俊傑’, ‘linjunjie’))
2.3.fetchmany
fetchmany(n)查詢指定前n條數據
代碼如下:
sql = "select * from userinfo" cursor.execute(sql) result = cursor.fetchmany(3) print(result)
結果:
((1, ‘劉德華’, ‘liudehua’), (3, ‘郭德綱’, ‘guodegang’), (4, ‘胡歌’, ‘huge’))
2.4.fetch小結
- 1.fetchone查詢單次數據,fetchall查詢所有數據,fetchmany(n)查詢指定n詞數據。
- 2.如果想要實現分頁的話,要先在查詢語句中limit 10數據,再用fetchall查詢指定的10條數據;而不能先fetchall所有數據,再一部分一部分的顯示。
- 3.相對來說,用的最多的是fetchone和fetchall
註:在fetch數據時按照順序進行,可以使用cursor.scroll(num,mode)來移動游標位置,如:
- cursor.scroll(1,mode=’relative’) # 相對當前位置移動
- cursor.scroll(2,mode=’absolute’) # 相對絕對位置移動
2.5.查詢數據字典形式顯示
為了方便查看,我們會將數據以字典格式顯示key和value
代碼如下:
import pymysql conn = pymysql.connect(host='localhost', user='root', password='root', database='test') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) sql = "select * from userinfo" cursor.execute(sql) result = cursor.fetchmany(3) print(result)
結果:
[{‘id’: 1, ‘username’: ‘劉德華’, ‘password’: ‘liudehua’}, {‘id’: 3, ‘username’: ‘郭德綱’, ‘password’: ‘guodegang’}, {‘id’: 4, ‘username’: ‘胡歌’, ‘password’: ‘huge’}]
2.6.新增數據的自增id
獲取新增數據的自增id:cursor.lastrowid,直接用
如果插入的為多行數據,則顯示的為最後一個自增的id
print(cursor.lastrowid)
3.pymysql刪改
增和查的註意點可能多一點,而pymysql刪與改的操作重點在於sql語句,其他只需要連接,執行,關閉連接即可。
3.1.改
代碼如下:
import pymysql conn = pymysql.connect(host='localhost', user='root', password='root', database='test') cursor = conn.cursor() sql = "update userinfo set name='李玉剛' where id=1" cursor.execute(sql) conn.commit() cursor.close() conn.close()
4.pymysql刪
4.1.刪
代碼如下:
import pymysql conn = pymysql.connect(host='localhost', user='root', password='root', database='test') cursor = conn.cursor() sql = "delete from userinfo where id=1" cursor.execute(sql) conn.commit() cursor.close()
視圖
為某個查詢語句設置別名,方便使用,即為視圖;
1.創建視圖
創建語句:
create view viewname as SQL
示例:
CREATE view v1 as SELECT * FROM userinfo WHERE id>5; CREATE view v1 as SELECT id,name FROM userinfo WHERE id>5;
2.修改視圖
修改語句:alter view viewname as SQL
eg:
alter view v1 as SELECT id,NAME from userinfo WHERE id >5;
3.刪除視圖
刪除語句:drop view viewname;
eg:
drop view v1;
4.查看視圖
查看語句:select from viewname;
eg:
select from v1; select id,name from v1;
5.視圖小結
給一個臨時表設置別名的過程就叫做創建視圖,別名就是視圖的名稱;實際存在的表比如userinfo就是物理表,而一個視圖就是虛擬表,虛擬表動態的從物理表中取數據,所以在物理表中插入數據後虛擬表也可能會隨之改變,但不能向虛擬表中插入數據;創建,修改,刪除視圖。
觸發器
對某個表進行【增/刪/改】操作的前後觸發一些操作即為觸發器,如果希望觸發增刪改的行為之前或之後做操作時,可以使用觸發器,觸發器用於自定義用戶對錶的行進行【增/刪/改】前後的行為。
1.創建觸發器
delimiter // # 修改終止符 CREATE TRIGGER tri_before_insert_userinfo BEFORE INSERT on userinfo for EACH ROW BEGIN insert into userinfolog(type,newname) VALUES('insert','姓名'); END// delimiter ; # 結束後將終止符修改回來
2.多次觸動觸發器
insert into userinfo(name,password) values('石超','shichao'),('林殊','linshu');
因為each row的存在,在一次SQL中插入兩行數據,會觸發兩次觸發器,即向userinfo插入數據前,會分別兩次向user插入數據。
3.自定義觸發器新插入數據
在前面我們寫的觸發器里,只能插入固定的數據,那麼我們可不可以在user中插入即將插入userinfo的數據呢?答案是可以的,如下:
delimiter // create trigger tri_after_delete_userinfo after delete on userinfo for each row begin insert into userinfolog(type,oldname) values(‘delete’,OLD.name); end // delimiter ; delimiter // create trigger tri_after_update_userinfo after update on userinfo for each row begin insert into userinfolog(type,oldname,newname) values('update',OLD.name,NEW.name); end // delimiter ;
註意:觸發器無法被修改,如果想要修改的話,可以先drop,再重新創建
delimiter // create trigger tri_after_update_userinfo after insert on userinfo for each row begin if NEW.operate = 'insert' then insert into userinfolog(type,newname) values('INSERT',NEW.name); elseif NEW.operate = 'delete' then insert into userinfolog(type,newname) values('delete',OLD.name); else insert into userinfolog(type,oldname,newname) values('update',OLD.name); end if; end // delimiter ;
函數
1、內置函數
MySQL中提供了許多內置函數,例如:
CURDATE(),可以查看當前時間;
使用:
select CURDATE()
2、自定義函數
delimiter \\ create function f1( i1 int, i2 int) returns int BEGIN declare num int; set num = i1 + i2; return(num); END \\ delimiter ;
3、刪除函數
drop function func_name();
4、執行函數
# 獲取返回值 set @i1=1; set @i2=2; select f1(@i1,@i2) into @j; SELECT @j; select f1(11,nid) ,name from tb2; # 在查詢中使用,對列的值函數計算後返回。
5、查看函數
show function status;
6、查看函數構建語句
show create function func_name\G
存儲過程
存儲過程是一個SQL語句集合,當主動去調用存儲過程時,其中內部的SQL語句會按照邏輯執行。
1、創建存儲過程
無參數存儲過程
– 創建存儲過程
delimiter // #修改結束符號,為// create procedure p1() BEGIN select * from t1; END// delimiter ;
– 執行存儲過程
call p1()
對於存儲過程,可以接收參數,其參數有三類:
- in 僅用於傳入參數用
- out 僅用於返回值用
- inout 既可以傳入又可以當作返回值
關於變數設置,對於調用存儲過程或者函數時,外部傳入參數或者獲取參數,需加符號@,例如set @t=1;在本次會話內這些帶@的變數都可以被獲取到。斷開連接後變數失效。
有參數存儲過程
– 創建存儲過程
delimiter \\ create procedure p1( in i1 int, in i2 int, inout i3 int, out r1 int ) BEGIN DECLARE temp1 int; DECLARE temp2 int default 0; set temp1 = 1; set r1 = i1 + i2 + temp1 + temp2; set i3 = i3 + 100; end\\ delimiter ;
– 執行存儲過程
SET @t2=3; CALL p1 (1, 2 ,@t1, @t2); SELECT @t1,@t2; delimiter // 將結束符號;修改為// DECLARE 聲明變數。如果沒有DEFAULT子句,初始值為NULL。用於內部變數申明。 SET 變數賦值。用於內部變數賦值,和傳參數時參數賦值。
2、刪除存儲過程
drop procedure proc_name;
3、執行存儲過程
– 無參數
call proc_name();
– 有參數,全in
call proc_name(1,2);
– 有參數,有in,out,inout
set @t1=3; call proc_name(1,2,@t1,@t2); import pymysql conn = pymysql.connect(host='127.0.0.1',user='root', password='root', db='t1') cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) cursor.callproc('p1', args=(1, 22, 3, 4)) # 執行存儲過程 cursor.execute("select @_p1_0,@_p1_1,@_p1_2,@_p1_3") # 獲取執行完存儲的參數 result = cursor.fetchall() conn.commit() cursor.close() conn.close() print(result)
4、查看存儲過程
列出所有的存儲過程
SHOW PROCEDURE STATUS;
5、查看存儲過程生成語句
查看存儲過程
SHOW CREATE PROCEDURE 存儲過程名\G
事務
事務用於將某些操作的多個SQL作為原子性操作,一旦有某一個出現錯誤,即可回滾到原來的狀態,從而保證資料庫數據完整性。
定義存儲過程:
delimiter \\ drop PROCEDURE if EXISTS p1; create PROCEDURE p1( OUT p_return_code tinyint ) BEGIN DECLARE exit handler for sqlexception -- 定義錯誤處理 BEGIN -- ERROR set p_return_code = 1; rollback; -- 回滾 END; DECLARE exit handler for sqlwarning -- 定義告警處理 BEGIN -- WARNING set p_return_code = 2; rollback; END; START TRANSACTION; -- 開始事務,使下麵的多條SQL語句操作變成原子性操作 UPDATE tb7 set licnese=(licnese-5) WHERE nid=21; UPDATE tb7 set licnese=(licnese+5) WHERE nid=22; COMMIT; -- SUCCESS set p_return_code = 0; END\\ delimiter ;
執行存儲過程:
call p1(@p); SELECT @p;
SQL防註入之動態SQL
在高級語言的DB API不提供防註入的參數化查詢功能時,可以使用這種方法來防止SQL註入。在pymysql中的調用點這裡。
定義存儲過程:
delimiter \\ DROP PROCEDURE IF EXISTS proc_sql \\ CREATE PROCEDURE proc_sql ( in nid1 INT, in nid2 INT, in callsql VARCHAR(255) ) BEGIN set @nid1 = nid1; set @nid2 = nid2; set @callsql = callsql; PREPARE myprod FROM @callsql; -- PREPARE prod FROM 'select * from tb2 where nid>? and nid<?'; 傳入的值為字元串,?為占位符 -- 用@nid1,和@nid2填充占位符 EXECUTE myprod USING @nid1,@nid2; DEALLOCATE prepare myprod; END\\ delimiter ;
調用存儲過程
set @nid1=12; set @nid2=15; set @callsql = 'select * from tb7 where nid>? and nid<?'; CALL proc_sql(@nid1,@nid2,@callsql)