Python 加快MySQL更新/插入语句的速度

Python 加快MySQL更新/插入语句的速度,python,mysql,Python,Mysql,如何从速度的角度改进以下功能?理想情况下,我希望将executemany用作此up过程的一部分 虽然函数运行良好,但我相信有一种更有效的方法可以做到这一点;检查值是否存在,并根据需要更新/插入 我需要每天对数百万数据进行此操作 def insert_or_update(self, profile_id, landing_page, keyword, position, impressions, clicks, ctr): ''' checks if a entry exists, if

如何从速度的角度改进以下功能?理想情况下,我希望将executemany用作此up过程的一部分

虽然函数运行良好,但我相信有一种更有效的方法可以做到这一点;检查值是否存在,并根据需要更新/插入

我需要每天对数百万数据进行此操作

def insert_or_update(self, profile_id, landing_page, keyword, position, impressions, clicks, ctr):
    ''' checks if a entry exists, if not it's inserted. If it is, the metrics
    are updated.
    '''
    try:
        self.cursor.execute('select id, position, impressions, clicks, ctr from temp where profile_id={} and keyword="{}" and landing_page="{}"'.format(profile_id, keyword, landing_page))
        data = self.cursor.fetchone()
        if data:
            row_id = data[0]
            sql_impressions = data[2] + impressions
            sql_clicks = data[3] + clicks
            sql_ctr = sum([data[4], ctr]) / len([data[4], ctr])
            # if the keyword/landing_page exists
            self.cursor.execute("update temp set position={}, impressions={}, clicks={}, ctr={} where id={}".format(position, sql_impressions, sql_clicks, sql_ctr, row_id))
            # Commit your changes in the database
            self.db.commit()
            return self.cursor.lastrowid
        else:
            # if the keyword/landing_page doesn't exist
            self.cursor.execute("insert into temp (profile_id, landing_page, keyword, position, impressions, clicks, ctr) values (%s, %s, %s, %s, %s, %s, %s)", (profile_id, landing_page, keyword, position, impressions, clicks, ctr))
            # Commit your changes in the database
            self.db.commit()
            return self.cursor.lastrowid
    except Exception as e:
        return e
        # Rollback in case there is any error
        self.db.rollback()
    finally:
        self.db.close()

如果需要执行数百万次,这里会出现一系列性能问题

  • 您一遍又一遍地准备相同的SQL语句,数百万次。它最好只准备一次,然后执行数百万次

  • 每次查询后,您都要断开与数据库的连接。这意味着您每次都需要重新连接,任何缓存的信息都会被丢弃。不要这样做,让它保持连接

  • 您在每行之后都要提交。这会减慢速度。而是在执行批处理后提交

  • select+update或insert可能作为单个upsert完成

  • 在临时表中插入这么多可能是性能问题

  • 如果表的索引太多,可能会减慢插入速度。有时最好删除索引,进行大批量更新,然后重新创建它们

  • 因为您将值直接放入SQL中,所以您的SQL是开放的


相反

  • 使用准备好的语句和绑定参数
  • 保持数据库连接
  • 批量更新
  • 仅在更新运行结束时提交
  • 执行
    UPDATE
    中的所有数学运算,而不是
    SELECT+math+UPDATE
  • 使用“向上插入”而不是
    选择
    ,然后
    更新
    插入
首先,准备好发言。这些让MySQL编译语句一次,然后重用它。其思想是编写一条语句,其中包含值的占位符

select id, position, impressions, clicks, ctr
from temp
where profile_id=%s and
      keyword=%s and 
      landing_page=%s
然后将值作为参数执行,而不是作为字符串的一部分

self.cursor.execute(
   'select id, position, impressions, clicks, ctr from temp where profile_id=%s and keyword=%s and landing_page=%s',
   (profile_id, keyword, landing_page)
)
这允许数据库缓存准备好的语句,而不必每次都重新编译它。它还避免了SQL注入攻击,聪明的攻击者可以创建一个更像SQL的值
“这里有更多SQL”
。这是一个非常非常常见的安全漏洞

注意,您可能需要使用。不要太担心,使用预先准备好的语句并不是最大的性能问题


接下来,您基本上要做的是添加到现有行,或者如果没有现有行,则插入一个新行。在一条语句中使用
UPSERT
、组合的
INSERT
UPDATE
,可以更有效地完成这项工作

要查看这是如何完成的,我们可以将您的
选择然后更新作为单个
更新
。计算是在SQL中完成的

    update temp
    set impressions = impressions + %s,
        clicks = clicks + %s,
        ctr = (ctr + %s / 2)
    where profile_id=%s and
          keyword=%s and
          landing_page=%s
您的插入保持不变

    insert into temp
        (profile_id, landing_page, keyword, position, impressions, clicks, ctr)
        values (%s, %s, %s, %s, %s, %s, %s)
在重复密钥更新时将它们合并为一个插入

    insert into temp
        (profile_id, landing_page, keyword, position, impressions, clicks, ctr)
        values (%s, %s, %s, %s, %s, %s, %s)
    on duplicate key update
    update temp
    set impressions = impressions + %s,
        clicks = clicks + %s,
        ctr = (ctr + %s / 2)
这取决于表的键定义为什么。如果您有
唯一(配置文件id、登录页面、关键字)
,那么它的工作原理应该与您的代码相同

即使您不能执行upsert,您也可以通过尝试
更新
,检查它是否更新了任何内容,以及是否没有执行
插入
来消除
选择


批量更新。与其调用执行一次更新和提交的子例程,不如将要更新的内容的大列表传递给它,并在循环中处理它们。您甚至可以利用以多个值运行同一语句。那就承诺吧

您可能可以批量执行
UPSERT
<代码>插入
可以一次获取多行。例如,这将插入三行

insert into whatever
    (foo, bar, baz)
values (1, 2, 3),
       (4, 5, 6), 
       (7, 8, 9)
您可能也可以通过重复密钥更新时插入
来执行相同的操作,从而减少与数据库通信的开销。请参阅(在PHP中,但您应该能够适应)


这牺牲了返回最后一个插入行的ID,但它们是中断。

如果需要执行数百万次,这里会出现一系列性能问题

  • 您一遍又一遍地准备相同的SQL语句,数百万次。它最好只准备一次,然后执行数百万次

  • 每次查询后,您都要断开与数据库的连接。这意味着您每次都需要重新连接,任何缓存的信息都会被丢弃。不要这样做,让它保持连接

  • 您在每行之后都要提交。这会减慢速度。而是在执行批处理后提交

  • select+update或insert可能作为单个upsert完成

  • 在临时表中插入这么多可能是性能问题

  • 如果表的索引太多,可能会减慢插入速度。有时最好删除索引,进行大批量更新,然后重新创建它们

  • 因为您将值直接放入SQL中,所以您的SQL是开放的


相反

  • 使用准备好的语句和绑定参数
  • 保持数据库连接
  • 批量更新
  • 仅在更新运行结束时提交
  • 执行
    UPDATE
    中的所有数学运算,而不是
    SELECT+math+UPDATE
  • 使用“向上插入”而不是
    选择
    ,然后
    更新
    插入
首先,准备好发言。这些让MySQL编译语句一次,然后重用它。其思想是编写一条语句,其中包含值的占位符

select id, position, impressions, clicks, ctr
from temp
where profile_id=%s and
      keyword=%s and 
      landing_page=%s
然后将值作为参数执行,而不是作为字符串的一部分

self.cursor.execute(
   'select id, position, impressions, clicks, ctr from temp where profile_id=%s and keyword=%s and landing_page=%s',
   (profile_id, keyword, landing_page)
)
这允许数据库缓存准备好的语句,而不必每次都重新编译它。它还避免了SQL注入攻击,聪明的攻击者可以在SQL注入攻击中使用craf