PyMysql操控
問題描述:
之前做數據庫模塊的時候用到了pymysql,測試中發現了一個問題,創建兩個程序,select.py從數據庫中不斷的讀取,insert.py在數據庫中插入多條數據,但是select.py程序查不到新添加的數據,像是做了緩存一樣,查到的數據永遠不變;只有重啟模塊,再次建立連接后,新添加的數據才能被查到;還原當時的代碼如下:
查詢:
# select.py 不斷的進行查詢 import pymysql import time # Connect to the database connection = pymysql.connect(host='192.168.1.134', port=3306, user='remote', password='tx_1234abc', db='Jefrey', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor, ) while True: with connection.cursor() as cursor: # Create a new record sql = " select * from users WHERE email=%s" # sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)" rows_count = cursor.execute(sql, ('webmaster@python.org')) result = cursor.fetchall() print(result) time.sleep(1)
插入:
# insert.py 進行數據插入 import pymysql.cursors import time # Connect to the database connection = pymysql.connect(host='192.168.1.134', port=3306, user='remote', password='tx_1234abc', db='Jefrey', charset='utf8mb4', # autocommit=True, cursorclass=pymysql.cursors.DictCursor,) try: # execute方法 with connection.cursor() as cursor: # Create a new record start = time.time() sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)" for i in range(5): cursor.execute(sql,('webmaster@python.org','test')) connection.commit() time.sleep(1) finally: connection.close()
運行30秒,查詢輸出打印:
() () () () ()
此時數據表中的數據確實是插入進去了:
重新啟動程序(重新建立的sql連接)后發現,之前添加的數據又都可以查到,但是新添加的數據又都查詢不到?!! 問題確實很懵逼 ,了解完下面內容后,問題會迎刃而解,開始補課! -》》相關的問題描述
剖析:
想要解決上述的問題,首先要明白mysql中事務這個概念,本地的mysql數據庫是默認安裝的,默認存儲引擎是(InnoDB),事務隔離級別是(REPEATABLE READ):
查看存儲引擎:
mysql> show engines; # 查看數據庫支持的存儲引擎 +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ | Engine | Support | Comment | Transactions | XA | Savepoints | +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ | FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL | | MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO | | MyISAM | YES | MyISAM storage engine | NO | NO | NO | | BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO | | CSV | YES | CSV storage engine | NO | NO | NO | | MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO | | ARCHIVE | YES | Archive storage engine | NO | NO | NO | | InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES | | PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO | +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ 9 rows in set (0.00 sec) mysql> show variables like '%storage_engine%'; # 當前的存儲引擎 +----------------------------+--------+ | Variable_name | Value | +----------------------------+--------+ | default_storage_engine | InnoDB | | default_tmp_storage_engine | InnoDB | | storage_engine | InnoDB | +----------------------------+--------+ 3 rows in set (0.00 sec)
查看當前的事務隔離級別:
mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set (0.00 sec)
重復讀(REPEATABLE READ):
InnoDB 的默認隔離級別。它可以防止任何被查詢的行被其他事務更改,從而阻止不可重復的讀取,而不是 幻讀取。它使用中度嚴格的鎖定策略,以便事務內的所有查詢都會查看同一快照中的數據,即數據在事務開始時的數據。

REPEATABLE READ The default isolation level for InnoDB. It prevents any rows that are queried from being changed by other transactions, thus blocking non-repeatable reads but not phantom reads. It uses a moderately strict locking strategy so that all queries within a transaction see data from the same snapshot, that is, the data as it was at the time the transaction started.
那么此時問題就找到了,跟當前的事務級別有關系的;當創建查詢事務時,事務一直沒有進行更新,每次查詢到的數據都是之前查詢結果的快照,下面會詳細介紹每種事務隔離級別的區別
解決:
知道了具體原因是事務級別的問題,導致查詢事務並沒有更新,那么針對事務隔離級別進行應對就可以了,此類問題有三種解決方案,修改事務隔離級別、每次查詢后更新事務、關閉數據庫的事務(慎選)
① 每次查詢后更新事務:
# 第一種方案,每次查詢后進行commit操作,進行事務更新 import pymysql import time # Connect to the database connection = pymysql.connect(host='192.168.1.134', port=3306, user='remote', password='tx_1234abc', db='Jefrey', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor, ) while True: with connection.cursor() as cursor: # Create a new record sql = " select * from users WHERE email=%s" # sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)" rows_count = cursor.execute(sql, ('webmaster@python.org')) result = cursor.fetchall() print(result) connection.commit() # 新增 time.sleep(1) # 第二種方案,創建connect連接時,autocommit=True,自動進行commit提交 import pymysql import time # Connect to the database connection = pymysql.connect(host='192.168.1.134', port=3306, user='remote', password='tx_1234abc', db='Jefrey', charset='utf8mb4', autocommit = True, #新增 cursorclass=pymysql.cursors.DictCursor, ) while True: with connection.cursor() as cursor: # Create a new record sql = " select * from users WHERE email=%s" # sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)" rows_count = cursor.execute(sql, ('webmaster@python.org')) result = cursor.fetchall() print(result) # connection.commit() time.sleep(1)
打印輸出,可以查到新更新的數據:
() () [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}] [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}] [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}, {u'password': u'test', u'id': 341, u'email': u'webmaster@python.org'}]
② 修改事務隔離級別(具體級別詳情下面介紹)
設置隔離級別命令 set [global/session] transaction isolation level xxxx; 如果使用global則修改的是數據庫的默認隔離級別,所有新開的窗口的隔離級別繼承自這個默認隔離級別如果使用session修改,則修改的是當前客戶端的隔離級別,和數據庫默認隔離級別無關。當前的客戶端是什么隔離級別,就能防止什么隔離級別問題,和其他客戶端是什么隔離級別無關。
# 設置事務隔離級別 mysql> set global transaction isolation level READ COMMITTED; Query OK, 0 rows affected (0.00 sec) mysql> exit Bye [root@localhost ~]# mysql Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 130 Server version: 5.6.36 MySQL Community Server (GPL) Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> select @@tx_isolation; +----------------+ | @@tx_isolation | +----------------+ | READ-COMMITTED | +----------------+
此時用最剛剛開始的代碼查詢,即使不更新事務依然可以查的到新添加的數據,打印輸出,可以查到新更新的數據:
() () [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}] [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}] [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}, {u'password': u'test', u'id': 341, u'email': u'webmaster@python.org'}]
③ 關閉數據庫的事務(修改存儲引擎)
# 查看存儲引擎 mysql> show create table users; +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | users | CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(255) COLLATE utf8_bin NOT NULL, `password` varchar(255) COLLATE utf8_bin NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=350 DEFAULT CHARSET=utf8 COLLATE=utf8_bin | +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) # 修改為MyISAM mysql> alter table users engine = MyISAM; Query OK, 5 rows affected (0.02 sec) Records: 5 Duplicates: 0 Warnings: 0 # 再次查看存儲引擎 mysql> show create table users; +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | users | CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `email` varchar(255) COLLATE utf8_bin NOT NULL, `password` varchar(255) COLLATE utf8_bin NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=350 DEFAULT CHARSET=utf8 COLLATE=utf8_bin | +-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
再次用最剛剛開始的代碼查詢,此時跟事務已經沒有什么關系了,可以查的到新添加的數據
() () [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}] [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}] [{u'password': u'test', u'id': 340, u'email': u'webmaster@python.org'}, {u'password': u'test', u'id': 341, u'email': u'webmaster@python.org'}]
數據庫事務:
事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功
事務特性:
- 原子性(Atomicity):原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發生,要么都不發生。
- 一致性(Consistency):事務前后數據的完整性必須保持一致。在事務執行之前數據庫是符合數據完整性約束的,無論事務是否執行成功,事務結束后的數據庫中的數據也應該是符合完整性約束的。在某一時間點,如果數據庫中的所有記錄都能保證滿足當前數據庫中的所有約束,則可以說當前的數據庫是符合數據完整性約束的。比如刪部門表前應該刪掉關聯員工(已經建立外鍵),如果數據庫服務器發生錯誤,有一個員工沒刪掉,那么此時員工的部門表已經刪除,那么就不符合完整性約束了,所以這樣的數據庫也就性能太差啦!
- 隔離性(Isolation):事務的隔離性是指多個用戶並發訪問數據庫時,一個用戶的事務不能被其它用戶的事務所干擾,多個並發事務之間數據要相互隔離。
- 持久性(Durability):持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。
隔離性:
將數據庫設計為串行化程的數據庫,讓一張表在同一時間內只能有一個線程來操作。如果將數據庫設計為這樣,那數據庫的效率太低了。所以數據庫的設計這沒有直接將數據庫設計為串行化,而是為數據庫提供多個隔離級別選項,使數據庫的使用者可以根據使用情況自己定義到底需要什么樣的隔離級別。
臟讀:
一個事務讀取到了另一個事務未提交的數據,這是特別危險的,要盡力防止。 a 1000 b 1000 a: start transaction; update set money=money+100 where name=b; b: start transaction; select * from account where name=b;--1100 commit; a: rollback; b: start transaction; select * from account where name=b;--1000
不可重復讀(開題遇到的問題就是此項):
在一個事務內讀取到另一個事務中更新的數據,多次讀取結果不同 a: start transaction; select 活期賬戶 from account where name=b;--1000 活期賬戶:1000 select 定期賬戶 from account where name=b;--1000 定期賬戶:1000 select 固定資產 from account where name=b;--1000 固定資產:1000 ------------------------------ b: start transaction; update set money=0 where name=b; commit; ------------------------------ select 活期+定期+固定 from account where name=b; --2000 總資產: 2000
幻讀:
是指在一個事務內讀取到了別的事務插入的數據,導致前后讀取不一致 b 1000 c 2000 d 3000 a: start transaction select sum(money) from account;---3000 3000 ------------------- d:start transaction; insert into account values(d,3000); commit; ------------------- select count(*)from account;---3 3 3000/3 = 1000 1000
更多區別-》》 http://blog.csdn.net/u013474436/article/details/53437220
四個隔離級別
Read Uncommitted(讀取未提交內容):
所有事務都可以看到其他未提交事務的執行結果
本隔離級別很少用於實際應用,因為它的性能也不比其他級別好多少
該級別引發的問題是——臟讀(Dirty Read):讀取到了未提交的數據

#首先,修改隔離級別 set tx_isolation='READ-UNCOMMITTED'; select @@tx_isolation; +------------------+ | @@tx_isolation | +------------------+ | READ-UNCOMMITTED | +------------------+ #事務A:啟動一個事務 start transaction; select * from tx; +------+------+ | id | num | +------+------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +------+------+ #事務B:也啟動一個事務(那么兩個事務交叉了) 在事務B中執行更新語句,且不提交 start transaction; update tx set num=10 where id=1; select * from tx; +------+------+ | id | num | +------+------+ | 1 | 10 | | 2 | 2 | | 3 | 3 | +------+------+ #事務A:那么這時候事務A能看到這個更新了的數據嗎? select * from tx; +------+------+ | id | num | +------+------+ | 1 | 10 | --->可以看到!說明我們讀到了事務B還沒有提交的數據 | 2 | 2 | | 3 | 3 | +------+------+ #事務B:事務B回滾,仍然未提交 rollback; select * from tx; +------+------+ | id | num | +------+------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +------+------+ #事務A:在事務A里面看到的也是B沒有提交的數據 select * from tx; +------+------+ | id | num | +------+------+ | 1 | 1 | --->臟讀意味着我在這個事務中(A中),事務B雖然沒有提交,但它任何一條數據變化,我都可以看到! | 2 | 2 | | 3 | 3 | +------+------+
Read Committed(讀取提交內容):
這是大多數數據庫系統的默認隔離級別(但不是MySQL默認的)
它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變
這種隔離級別出現的問題是——不可重復讀(Nonrepeatable Read):不可重復讀意味着我們在同一個事務中執行完全相同的select語句時可能看到不一樣的結果。
導致這種情況的原因可能有:(1)有一個交叉的事務有新的commit,導致了數據的改變;(2)一個數據庫被多個實例操作時,同一事務的其他實例在該實例處理其間可能會有新的

#首先修改隔離級別 set tx_isolation='read-committed'; select @@tx_isolation; +----------------+ | @@tx_isolation | +----------------+ | READ-COMMITTED | +----------------+ #事務A:啟動一個事務 start transaction; select * from tx; +------+------+ | id | num | +------+------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +------+------+ #事務B:也啟動一個事務(那么兩個事務交叉了) 在這事務中更新數據,且未提交 start transaction; update tx set num=10 where id=1; select * from tx; +------+------+ | id | num | +------+------+ | 1 | 10 | | 2 | 2 | | 3 | 3 | +------+------+ #事務A:這個時候我們在事務A中能看到數據的變化嗎? select * from tx; ---------------> +------+------+ | | id | num | | +------+------+ | | 1 | 1 |--->並不能看到! | | 2 | 2 | | | 3 | 3 | | +------+------+ |——>相同的select語句,結果卻不一樣 | #事務B:如果提交了事務B呢? | commit; | | #事務A: | select * from tx; ---------------> +------+------+ | id | num | +------+------+ | 1 | 10 |--->因為事務B已經提交了,所以在A中我們看到了數據變化 | 2 | 2 | | 3 | 3 | +------+------+
Repeatable Read(可重讀):
這是MySQL的默認事務隔離級別
它確保同一事務的多個實例在並發讀取數據時,會看到同樣的數據行
此級別可能出現的問題——幻讀(Phantom Read):當用戶讀取某一范圍的數據行時,另一個事務又在該范圍內插入了新行,當用戶再讀取該范圍的數據行時,會發現有新的“幻影” 行
InnoDB和Falcon存儲引擎通過多版本並發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題

#首先,更改隔離級別 set tx_isolation='repeatable-read'; select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ #事務A:啟動一個事務 start transaction; select * from tx; +------+------+ | id | num | +------+------+ | 1 | 1 | | 2 | 2 | | 3 | 3 | +------+------+ #事務B:開啟一個新事務(那么這兩個事務交叉了) 在事務B中更新數據,並提交 start transaction; update tx set num=10 where id=1; select * from tx; +------+------+ | id | num | +------+------+ | 1 | 10 | | 2 | 2 | | 3 | 3 | +------+------+ commit; #事務A:這時候即使事務B已經提交了,但A能不能看到數據變化? select * from tx; +------+------+ | id | num | +------+------+ | 1 | 1 | --->還是看不到的!(這個級別2不一樣,也說明級別3解決了不可重復讀問題) | 2 | 2 | | 3 | 3 | +------+------+ #事務A:只有當事務A也提交了,它才能夠看到數據變化 commit; select * from tx; +------+------+ | id | num | +------+------+ | 1 | 10 | | 2 | 2 | | 3 | 3 | +------+------+
Serializable(可串行化):
這是最高的隔離級別
它通過強制事務排序,使之不可能相互沖突,從而解決幻讀問題。簡言之,它是在每個讀的數據行上加上共享鎖。
在這個級別,可能導致大量的超時現象和鎖競爭

#首先修改隔離界別 set tx_isolation='serializable'; select @@tx_isolation; +----------------+ | @@tx_isolation | +----------------+ | SERIALIZABLE | +----------------+ #事務A:開啟一個新事務 start transaction; #事務B:在A沒有commit之前,這個交叉事務是不能更改數據的 start transaction; insert tx values('4','4'); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction update tx set num=10 where id=1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
注:
安全性考慮:Serializable>Repeatable read>Read committed>Read uncommitted
數據庫效率:Read uncommitted>Read committed>Repeatable read>Serializable
一般情況下,我們會使用Repeatable read、Read committed mysql數據庫默認的數據庫隔離級別Repeatable read