Python 加快MySQL更新/插入语句的速度
如何从速度的角度改进以下功能?理想情况下,我希望将executemany用作此up过程的一部分 虽然函数运行良好,但我相信有一种更有效的方法可以做到这一点;检查值是否存在,并根据需要更新/插入 我需要每天对数百万数据进行此操作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
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
- 使用“向上插入”而不是
,然后选择
或更新
插入
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