python 中 pymysql拼接执行入参踩坑(%跟%%的区别)总结

一、对于不同类型的入参,有不同的占位符,但是用多了会发现,%s才是真理
二、sql拼接可以用%s,也可以用format,如果不考虑sql注入风险问题,个人建议使用format,可以接受dict为入参进行匹配。
三、在考虑sql注入风险的情况下,可以将拼接好的sql跟入参分开,调用cur.execute(sql, params)来规避sql注入风险。
    需要注意的是,这里有两种使用方式,建议使用第二种:
        1、采用sql拼接的方式,拼接时用 xx= %s的方式,入参格式为元组或者list
            如下举例:
            

            sql = "select count(*) as sys_num from test where true "
            params = []
            if param1:
                sql += " and param1 = %s "
                params.append(param1)
            if param2:
                sql += " and (param2=%s) "
                params.append(param2)
            cur.execute(sql, params)


        2、采用sql拼接的方式,拼接时用 xx=%(xx)s的方式, 入参格式为dict
            如下举例:
          

            sql = "select count(*) as sys_num from test where true "
            params = {"param1": "xx", "param2": "xxx"}
            if param1:
                sql += " and param1 = %(param1)s "
            if param2:
                sql += " and (param2=%(param2)s) "
            cur.execute(sql, params)


四、需要特别注意的是,当你的sql中包含了date_format(date,'%Y-%m')等带%号的时候,有以下几种情况需要注意:
    1、如果你将sql完全拼接好(包括入参)再通过只传入sql调用cur.execute(sql,param)方法执行的时候(这里的param默认为None),这里的'%Y-%m'不需要改成'%%Y-%%m'。
        如:
            

            sql = """
                    SELECT *  FROM test
                    WHERE date_time IN (SELECT max(date_time) FROM test)
                    and date_format(submit_date,'%Y') = date_format('{}','%Y')
                    GROUP BY status;
                """.format(date_time)
            cur.execute(sql)
            # cur.execute(sql, {})
            result = cur.fetchall()


            需要注意:上面的cur.execute(sql)如果改成cur.execute(sql,param)并且param不为None, 这时候就需要改成%%。
    2、如果你拼接的sql不包括入参(而是通过%s或者其他占位符),再通过传入sql和param来调用cur.execute(sql,param)执行的时候,这里的'%Y-%m'需要改成'%%Y-%%m'。
          

            sql = """
                    SELECT *  FROM test
                    WHERE date_time IN (SELECT max(date_time) FROM test)
                    and date_format(submit_date,'%%Y') = date_format(%(date_time)s,'%%Y')
                    GROUP BY status;
                """
            cur.execute(sql, {"date_time": '2019-08-09'})
            result = cur.fetchall()


    3、为什么呢?通过查看源码发现,cur.execute(sql,param)方法param默认为None,当param为None时,默认你传入的sql是完整的,不进行预编译,所以不需要两个%%来防止被编译。
    当param不为None时,默认你的sql是不完整的,需要通过params对你的sql进行匹配获得完整的sql,这时会对sql进行预编译,预编译会对%x等占位符进行处理,这时你的sql中有%Y,%m等不合法的占位符
    就会直接报错,所以这种情况下需要使用%%来防止被编译。

五、需要注意的是,当你的sql想动态接收数据库表名或者字段名时,不能使用sql+params调用cur.execute(sql, {"table_name": 'aaaaa'})这种方式,因为pymysql会判断入参类型,如果是字符串的会补上'',表名加''会导致sql执行报错。

如果一定要动态接收表名或者字段名,需要在调用cur.execute()前提前拼接好,可以使用%(table_name)s或者format()。
六、总结:
    1、占位符尽量都使用%s
    2、建议使用防止sql注入的方式,通过将分开的sql,param作为入参调用cur.execute(sql,param),这里的param可以为元组、列表和字典。
    3、使用%%是为了防止被mysql预编译导致报错,cur.execute(sql,param)方法中params默认为None,不进行编译,当params不为None时,就会对你的sql进行预编译(无论你的sql是否已经拼接完整)
    这时就需要使用%%来防止被编译。

    4、动态接收表名或者字段名不能sql+params调用cur.execute(sql, params)这种方式,需要在调用cur.execute()前提前拼接好,可以使用%(table_name)s或者format()。

 


    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章