通過Python用pymysql,通過sshtunnel模塊ssh連接遠程數據庫。


最近在改自己的一個小腳本,准備從本地shelve文檔,josn數據等格式,轉移到MySQL和redis。

服務器本來就是通過SHH登錄的,但我不想開放MySQL的外網端口,要設置,還怕不安全,我的MySQL就幾位數的密碼。

查了下資料,Python第三方有ssh登錄的模塊sshtunnel

這是我參考的鏈接:https://www.cnblogs.com/luyingfeng/p/6386093.html

我先整版拷貝過來。

安全起見,數據庫的訪問多半是要做限制的,所以就有一個直接的問題是,往往多數時候,在別的機器上(比如自己本地),是不能訪問數據庫的,給日常使用造成了很大不便。所以前幾天做了個需求,是希望在任何機器上都可以ssh到某台在數據庫白名單的機器上,然后訪問數據庫。

經人推薦,查詢了一個工具叫sshtunnel ,需要在你想要登錄數據庫的機器上安裝。

sshtunnel的基本介紹 : http://sshtunnel.readthedocs.io/en/latest/?badge=latest

這里邊的圖2,正好是我們描述的場景,數據庫在遠程某台服務器上,只能同在一起的另一台服務器才能訪問,但是我們本地就需要ssh到這台服務器上

 

但是這個里邊涉及到的代碼是服務器相關,遠程連接的是服務器,我們需要的是遠程服務器的數據庫,這個也一樣,只需要變更代碼中后半部分就可以了。

原代碼是下邊這樣:

復制代碼
 1 import paramiko  2 from sshtunnel import SSHTunnelForwarder  3  4 with SSHTunnelForwarder(  5 (REMOTE_SERVER_IP, 443),  6 ssh_username="",  7 ssh_pkey="/var/ssh/rsa_key",  8 ssh_private_key_password="secret",  9 remote_bind_address=(PRIVATE_SERVER_IP, 22), 10 local_bind_address=('0.0.0.0', 10022) 11 ) as tunnel: 12 client = paramiko.SSHClient() 13  client.load_system_host_keys() 14  client.set_missing_host_key_policy(paramko.AutoAddPolicy()) 15 client.connect('127.0.0.1', 10022) 16 # do some operations with client session 17  client.close() 18 19 print('FINISH!')
復制代碼

連接mysql數據庫

連接mysql數據庫的時候,看到網上有一位程序員已經實現了: python 使用mysqldb模塊通過ssh隧道連接mysql

代碼如下:

復制代碼
 1 import MySQLdb  2 from sshtunnel import SSHTunnelForwarder  3  4 with SSHTunnelForwarder(  5 ('sshhost.domain.com', 22), #B機器的配置  6 ssh_password="sshpasswd",  7 ssh_username="sshusername",  8 remote_bind_address=('mysqlhost.domain.com', mysql.port)) as server: #A機器的配置  9 10 conn = MySQLdb.connect(host='127.0.0.1', #此處必須是是127.0.0.1 11 port=server.local_bind_port, 12 user='user', 13 passwd='password', 14 db='dbname')
復制代碼

 然后接下來的查詢什么的,直接寫在with那里邊,與conn對齊就可以了。

對於我這邊來說有一個問題是,因為我們對於數據庫連接這一部分,往往是在一個單獨的函數里,與其他數據庫的查詢插入刪除更新操作往往不在一起,這樣的話,with as 有個特點就是,離開這塊作用域,對象就被銷毀掉了,別的函數里是沒法用的,也就會出現一種情況是,連接上了,但是對象又給銷毀掉了,結果查詢的時候直接顯示這個錯誤:OperationalError: (2006, 'MySQL server has gone away'), 而網上查詢這個錯誤,多半說的是因為你查詢的 sql操作的時間過長,或者是傳送的數據太大 ,但是我這個地方實際上就是因為出了with as 的作用域,導致連接又給關閉掉了,所以出現這樣的結果。

關於 with as ,有篇文章寫得很詳盡。理解Python中的with…as…語法

所以我把上邊那個ssh代碼改掉了,像是sshtunnel文檔里邊圖一所對應的代碼,一樣,將SSHTunnelForwarder出來的對象賦值給server,然后啟動server,然后進行一系列操作之后,再stop掉。

本來數據庫連接我們寫成了一個單獨的函數,改了之后,直接也還放在這個函數里就好了,替代原來的connect語句。

復制代碼
 1 def connect(self):  2 '''  3  self.client = MySQLdb.connect(host=self.server, port=self.port, user=self.user,  4  passwd=self.password, db=self.database,  5  charset=self.charset)  6  # log.info('Connect to MySQL Server: ' + self.server)  7 '''  8  9 server = SSHTunnelForwarder( 10 ('sshhost.domain.com', 22), # B機器的配置 11 ssh_password='ssh_password', 12 ssh_username='ssh_username', 13 remote_bind_address=('mysqlhost.domain.com', mysql.port) 14  ) 15  server.start() 16 17 self.client = MySQLdb.connect(host='127.0.0.1', # 此處必須是是127.0.0.1 18 port=server.local_bind_port, 19 user='username', 20 passwd='password', 21 db='dbname')
復制代碼

然后在進行查詢更新刪除等操作的時候,先連接一下數據庫就好了,用self.client.

連接sqlserver數據庫

跟mysql的一致,但是db那里要注意,SQLServer的是database, 然后是pymssql.connect就可以了,但是這個地方還要說我踩過的一個坑,我寫完sqlserver之后怎么連接都連不上數據庫,后來才發現是版本的問題,我把本地SQLServer更新了之后就可以了。感覺版本是個大坑

 

上面是原文的作者寫的。其實這個模塊本來是向通過中間服務器登錄目標服務器的。但我的中間服務器就是目標服務器了。

import pymysql
from sshtunnel import SSHTunnelForwarder


class DataBaseHandle:
    ''' 定義一個 MySQL 操作類'''

    def __init__(self, host='127.0.01', username='xxx', password='xxx'
                 , database='xxx', port=10022):
        '''初始化數據庫信息並創建數據庫連接'''
        self.w_server = SSHTunnelForwarder(
                # 中間服務器地址
                ("xxx.64.47.xxx", 22),
                ssh_username="xxx",
                ssh_pkey="~/.ssh/id_rsa",
                # ssh_private_key_password="~/.ssh/id_rsa",
                # 目標的地址與端口,因為目標地址就是中間地址所以寫127.0.0.1或者localhost
                remote_bind_address=('127.0.0.1', 3306),
                # 本地的地址與端口
                local_bind_address=('0.0.0.0', 10022)
                )
        # 啟動ssh實例,后續的MySQL網絡連接都將在這個環境下運行。
        self.w_server.start()
        # 后面開始對MySQL的數據進行初始化
        self.host = host
        self.username = username
        self.password = password
        self.database = database
        self.port = port
        self.db = pymysql.connect(host=self.host,
                                  user=self.username,
                                  password=self.password,
                                  database=self.database,
                                  port=self.port,
                                  charset='utf8')

    #  這里 注釋連接的方法,是為了 實例化對象時,就創建連接。不許要單獨處理連接了。
    #
    # def connDataBase(self):
    #     ''' 數據庫連接 '''
    #
    #     self.db = pymysql.connect(self.host,self.username,self.password,self.port,self.database)
    #
    #     # self.cursor = self.db.cursor()
    #
    #     return self.db

    def insertDB(self, sql):
        ''' 插入數據庫操作 '''

        self.cursor = self.db.cursor()

        try:
            # 執行sql
            self.cursor.execute(sql)
            # tt = self.cursor.execute(sql)  # 返回 插入數據 條數 可以根據 返回值 判定處理結果
            # print(tt)
            self.db.commit()
        except:
            # 發生錯誤時回滾
            self.db.rollback()
        finally:
            self.cursor.close()

    def deleteDB(self, sql):
        ''' 操作數據庫數據刪除 '''
        self.cursor = self.db.cursor()

        try:
            # 執行sql
            self.cursor.execute(sql)
            # tt = self.cursor.execute(sql) # 返回 刪除數據 條數 可以根據 返回值 判定處理結果
            # print(tt)
            self.db.commit()
        except:
            # 發生錯誤時回滾
            self.db.rollback()
        finally:
            self.cursor.close()

    def updateDb(self, sql):
        ''' 更新數據庫操作 '''

        self.cursor = self.db.cursor()

        try:
            # 執行sql
            self.cursor.execute(sql)
            # tt = self.cursor.execute(sql) # 返回 更新數據 條數 可以根據 返回值 判定處理結果
            # print(tt)
            self.db.commit()
        except:
            # 發生錯誤時回滾
            self.db.rollback()
        finally:
            self.cursor.close()

    def selectDb(self, sql):
        ''' 數據庫查詢 '''
        self.cursor = self.db.cursor()
        try:
            self.cursor.execute(sql)  # 返回 查詢數據 條數 可以根據 返回值 判定處理結果

            data = self.cursor.fetchall()  # 返回所有記錄列表

            print(data)

            # 結果遍歷
            for row in data:
                sid = row[0]
                name = row[1]
                # 遍歷打印結果
                print('sid = %s,  name = %s' % (sid, name))
        except:
            print('Error: unable to fecth data')
        finally:
            self.cursor.close()

    def closeDb(self):
        ''' 數據庫連接關閉 '''
        self.db.close()
        self.w_server.close()



if __name__ == '__main__':
    DbHandle = DataBaseHandle()
    DbHandle.selectDb('SELECT VERSION()')
    DbHandle.closeDb()

 

這個操作數據庫的類也是我網上找來的,感覺寫的還行,就直接拿來用了。

最后記得,在退出的時候記得關閉ssh與MySQL的連接。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM