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秒后。下一篇内容,我会聊下,我们是如何使用缓存的。

 祝大家,周末愉快。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值