1. 程式人生 > >python程式碼調優,誰在呼叫資料庫連線?

python程式碼調優,誰在呼叫資料庫連線?

    最新在優化一個複雜的頁面,複雜到什麼情況呢,光請求就可能有30到50個,這種情況下,如果每個請求大概有3,5個數據庫請求的話,我們開啟這個頁面,就可能產生150個數據庫請求,開啟這個頁面時間超過30秒。。。。老大說到了無法容忍的地步,下發指令,2秒開啟頁面,做不到就滾蛋(完成了會所嫩模)。

   苦逼的碼農,為了養家餬口(嫩模),只能分析這個頁面。這個頁面主要的問題是,有N個圖表組成,每個圖表的資料是動態的,還會根據訪問者的不同返回不同資料(做了資料許可權)。那麼這些都是會產生資料庫的查詢,要加快速度,那資料庫的連線肯定是不能要的,統統都要轉移到快取資料庫(Redis)中。

    那麼,問題來了,如何在一個請求中,看出它執行了哪些sql語句呢?方案應該是很多的,我說下我們的方案。

    python連線mysql,目前主流的應該是pymysql和MysqlDB,不管哪個,你都有一個最終執行SQL的地方,我們在這個地方需要用一個上下文的東西去記錄sql語句,示例程式碼如下:    

    def _execute(self, sql, params=None):
        """
        check if connection is alive. if not, reconnect
        :param sql:
        :param params:
        :rtype Cursor:
        """
        if hasattr(g, "profiling"):
            g.sqls.append(
                {
                    "sql": sql,
                    "params": params,
                    "db": self.db,
                }
            )
        self.cur.execute(sql, params)
        return self.cur

    每執行一條sql語句(開啟除錯模式的情況下,profiling有值),就會往g.sqls陣列中追加一條記錄,返回前端時新增上這個g.sqls就可以了。(說明下我們的g,是一個上下文變數,他的生命週期是一個請求的開始到請求的結束。參考Flask)

    前端拿到資料:

    我們就可以清楚的看到,每一個請求到底執行了多少的sql語句。

    知道執行了多少sql語句,那麼問題來了,怎麼去找到這些sql語句的呼叫地方呢?我們需要用到python提供的堆疊資訊,追蹤原始呼叫的地方。一說到堆疊大家可能都先想到日誌,沒錯,跟日誌的堆疊差不多,這裡有篇文章講的是日誌堆疊(python優雅的記錄日誌(堆疊追蹤)

    我這裡使用的是traceback模組,python的標準模組,它其實也是呼叫sys.exc_info()資訊。這裡用traceback主要原因是它做了一層封裝,支援一些引數,用起來更加方便,推薦大家使用。程式碼如下:

    def _execute(self, sql, params=None):
        """
        check if connection is alive. if not, reconnect
        :param sql:
        :param params:
        :rtype Cursor:
        """
        if hasattr(g, "profiling"):
            import traceback

            extracted_list = traceback.extract_stack(limit=10)
            g.sqls.append(
                {
                    "sql": sql,
                    "params": params,
                    "db": self.db,
                    "stacks": [
                        "{fname} {lineno} {name}".format(fname=frame.filename, lineno=frame.lineno, name=frame.name)
                        if isinstance(frame, FrameSummary)
                        else "{fname} {lineno} {name}".format(fname=frame[0], lineno=frame[1], name=frame[2])
                        for frame in extracted_list
                        if (isinstance(frame, (list, tuple)) and not frame[0].find('site-packages') > 0)
                        or (isinstance(frame, FrameSummary) and not frame.filename.find('site-packages') > 0)
                    ],
                }
            )
        self.cur.execute(sql, params)
        return self.cur

    程式碼比較簡單,就是extracted_list拿到堆疊資訊,用format的方式做一下格式化輸出,畢竟是要給人看的嘛。 traceback.extract_stack(limit=10) 表示最多追蹤10層,這個因程式碼複雜程度而異。太深看了頭暈,太淺看不到呼叫的源頭。

再次請求,看到如下圖:

這樣就很請求,是哪個函式在進行資料庫查詢了。(後來還加了一些資料庫執行時間等的功能。)

接下來,我們就可以根據返回,一步一步消滅db查詢咯,使用Redis替換,還是蠻有成就感的,當你看到一個頁面從30秒優化到2秒後。下一篇內容,我會聊下,我們是如何使用快取的。

 祝大家,週末愉快。