關於Python的pymssql模塊,以前研究時總結了「pymssql默認關閉自動模式開啓事務行爲淺析」這篇博客,可是在測試過程當中又發現了幾個問題,下面對這些問題作一些淺析,若有不足或不正確的地方,敬請指出。html
1: pymssql的commit函數能夠提交兩次或屢次python
Connection.commit():sql
Commit current transaction. You must call this method to persist your data if you leave autocommit at its default value, which is False數據庫
咱們知道pymssql模塊裏面有commit函數表示提交事務,因爲某個特殊緣由,測試過程當中發現執行屢次commit都OK,不會報錯,以下代碼所示。app
# -*- coding: utf-8 -*-
'''
-------------------------------------------------------------------------------------------
-- Script Name : TranTest.py
-------------------------------------------------------------------------------------------
'''
import pymssql
import logging
import os.path
import os
import base64
from cryptography.fernet import Fernet
key=bytes(os.environ.get('key'),encoding="utf8")
cipher_suite = Fernet(key)
with open('/home/konglb/python/conf/ms_db_conf.bin', 'rb') as file_object:
for line in file_object:
encryptedpwd = line
decrypt_pwd = (cipher_suite.decrypt(encryptedpwd))
password_decrypted = bytes(decrypt_pwd).decode("utf-8") #convert to string
env_db_user=os.environ.get('db_user')
db_user=base64.b64decode(bytes(env_db_user, encoding="utf8"))
dest_db_conn = pymssql.connect(host=os.environ.get('db_host'),
user=bytes.decode(db_user),
password=password_decrypted,
database='master',
charset="utf8");
sub_cursor = dest_db_conn.cursor(as_dict=True)
sub_cursor.execute('SELECT COUNT(*) AS RecordNum FROM msdb.dbo.sysmail_account')
result_rows =sub_cursor.fetchone()
print(result_rows["RecordNum"])
dest_db_conn.commit()
dest_db_conn.commit()
dest_db_conn.close()
其實咱們用SQL Profile跟蹤一下就會知道,多執行一次commit,至關於在SQL Server數據庫多執行了一次下面SQL,顯然不會出現什麼問題,可是也沒有什麼用處,因此這個應該只提交一次就OK了。這個問題,其實一開始對於我來講還有點震驚。瞭解過原理後,其實發現也就那麼一回事。若是你是驅動的開發者而言,也不可能讓第二次commit報錯,若是這樣的話,那麼程序的健壯性就有問題了。函數
BEGIN TRAN測試
COMMIT TRAN;fetch
2: pymssql的close函數能夠關閉屢次?ui
Connection.close() :Close the connectionthis
關於pymssql中的close函數表示關閉數據庫鏈接,第一次執行就已經關閉了數據庫鏈接,執行第二次close沒有報任何錯誤,可是若是在鏈接關閉後,再執行查詢之類的操做,就會報「pymssql.InterfaceError: Connection is closed」這類錯誤,以下所示,簡單修改上面代碼,就能夠測試、驗證:
dest_db_conn.commit()
dest_db_conn.close()
sub_cursor.execute('SELECT COUNT(*) AS RecordNum FROM msdb.dbo.sysmail_account')
dest_db_conn.close()
#python TranTest.py
Traceback (most recent call last):
File "TranTest.py", line 45, in <module>
sub_cursor.execute('SELECT COUNT(*) AS RecordNum FROM msdb.dbo.sysmail_account')
File "src/pymssql.pyx", line 448, in pymssql.Cursor.execute
File "src/pymssql.pyx", line 238, in pymssql.Connection._conn.__get__
pymssql.InterfaceError: Connection is closed.
我的猜想驅動程序已經關閉數據庫連接了,第二次執行close函數時,可能驅動底層檢測到數據庫鏈接已經關閉,直接退出了,不作任何操做。可是若是數據庫鏈接關閉後,再去執行相關SQL,此時就會報「Connection is closed」這類錯誤了。
3: 若是忘記提交或回滾事務,那麼腳本執行完成後會回滾嗎? 何時回滾呢? 另外,它會阻塞其它會話嗎? 阻塞的時間有多長?
# -*- coding: utf-8 -*-
'''
-------------------------------------------------------------------------------------------
-- Script Name : TranTest.py
-------------------------------------------------------------------------------------------
'''
import pymssql
import logging
import os.path
import os
import base64
from cryptography.fernet import Fernet
key=bytes(os.environ.get('key'),encoding="utf8")
cipher_suite = Fernet(key)
with open('/home/konglb/python/conf/ms_db_conf.bin', 'rb') as file_object:
for line in file_object:
encryptedpwd = line
decrypt_pwd = (cipher_suite.decrypt(encryptedpwd))
password_decrypted = bytes(decrypt_pwd).decode("utf-8") #convert to string
env_db_user=os.environ.get('db_user')
db_user=base64.b64decode(bytes(env_db_user, encoding="utf8"))
dest_db_conn = pymssql.connect(host=os.environ.get('db_host'),
user=bytes.decode(db_user),
password=password_decrypted,
database='master',
charset="utf8");
sub_cursor = dest_db_conn.cursor(as_dict=True)
sub_cursor.execute("UPDATE TEST SET NAME='KKK' WHERE ID=100")
#dest_db_conn.commit()
#dest_db_conn.close()
dest_db_conn.close()
爲了搞清楚上面這些問題,我修改了上面腳本,執行後,我去查詢數據庫, 發現即便上面的Python腳本沒有提交事務,可是不會阻塞其它會話(實際上是由於事務已經回滾了),對應的會話已經不存在了。猜想是由於Python腳本執行完成後,關閉了TCP層的鏈接而觸發底層驅動關閉數據庫鏈接(在關閉數據庫鏈接以前,回滾了沒有提交的事務)。
那麼怎麼驗證呢? 很簡單,咱們使用休眠函數sleep,在關閉數據庫聯機(dest_db_conn.close()) 前讓其休眠100秒,
dest_db_conn = pymssql.connect(host=os.environ.get('db_host'),
user=bytes.decode(db_user),
password=password_decrypted,
database='master',
charset="utf8");
sub_cursor = dest_db_conn.cursor(as_dict=True)
sub_cursor.execute("UPDATE TEST SET NAME='KKK' WHERE ID=100")
time.sleep(100)
dest_db_conn.close()
而後在這期間,咱們就能夠查看會話信息、查看未提交的事務,構造阻塞會話等等。以下所示:
SELECT * FROM sys.sysprocesses WHERE loginame='xxx'
DECLARE @tab TABLE
(
NAME VARCHAR(100) ,
value VARCHAR(200)
);
INSERT INTO @tab
EXEC ( 'DBCC OPENTRAN WITH TABLERESULTS'
);
SELECT NAME ,
CAST(value AS DATETIME) startDate ,
GETDATE() currentDate ,
DATEDIFF(s, CAST(value AS DATETIME), GETDATE()) diffsecond
FROM @tab
WHERE NAME IN ( 'OLDACT_STARTTIME' );
SELECT spid ,
blocked ,
DB_NAME(sp.dbid) AS DBName ,
program_name ,
waitresource ,
lastwaittype ,
sp.loginame ,
sp.hostname ,
A.[text] AS [TextData] ,
SUBSTRING(A.text, sp.stmt_start / 2,
( CASE WHEN sp.stmt_end = -1 THEN DATALENGTH(A.text)
ELSE sp.stmt_end
END - sp.stmt_start ) / 2) AS [current_cmd]
FROM sys.sysprocesses AS sp
OUTER APPLY sys.dm_exec_sql_text(sp.sql_handle) AS A
WHERE spid = ( SELECT CASE WHEN ISNUMERIC(value) = 0 THEN -1
ELSE value
END
FROM @tab
WHERE NAME IN ( 'OLDACT_SPID' )
);
那麼爲何說是Python執行完成後,關閉TCP鏈接觸發了底層驅動作這個事情呢? 你測試時,發現執行完腳本後,都會有一個Audit Logout,以下截圖所示,另外,你也能夠將上面腳本的休眠函數和關閉數據庫鏈接註釋掉,你會發現,即便不關閉數據庫鏈接,Python腳本執行完成後,事務也回滾了,數據庫鏈接也關閉了。其實若是你進行了上面測試,第三個問題已經基本不用回答了。顯然已經不言而喻了
#time.sleep(100)
#dest_db_conn.close()
Audit Logout:Records all new disconnect events since the trace started, such as when a client issues a disconnect command