大量更新的脚本优化
Script optimization for massive updates
我正在编写一个脚本,该脚本创建一些数据的散列并将其保存在数据库中。所需的数据来自 SQL 查询,该查询将大约 30 万行与 50 万行连接起来。在解析结果时,我使用第二个连接处理程序创建哈希值并在数据库中更新(使用第一个连接处理程序给我一个 "Unread results" 错误)。
经过大量调查,我发现在性能方面给我最好的结果如下:
- 每 x 次迭代重新启动 select 查询。否则一段时间后更新会变慢
- 每 200 个查询才提交一次,而不是每次查询都提交一次
- 用于 select 查询的表是 MyISAM,并使用连接中使用的主键和字段进行索引。
- 我的散列 table 是 InnoDB,只有主键 (id) 被索引。
这是我的脚本:
commit = ''
stillgoing = True
limit1 = 0
limit2 = 50000
i = 0
while stillgoing:
j = 0
# rerun select query every 50000 results
getProductsQuery = ("SELECT distinct(p.id), p.desc, p.macode, p.manuf, "
"u.unit, p.weight, p.number, att1.attr as attribute1, p.vcode, att2.attr as attribute2 "
"FROM p "
"LEFT JOIN att1 on p.id = att1.attid and att1.attrkey = 'PARAM' "
"LEFT JOIN att2 on p.id = att2.attid and att2.attrkey = 'NODE' "
"LEFT JOIN u on p.id = u.umid and u.lang = 'EN' "
"limit "+str(limit1)+", "+str(limit2))
db.query(getProductsQuery)
row = db.fetchone()
while row is not None:
i += 1
j += 1
id = str(row[0])
# create hash value
to_hash = '.'.join( [ helper.tostr(s) for s in row[1:]] )
hash = hashlib.md5(to_hash.encode('utf-8')).hexdigest()
# set query
updQuery = ("update hashtable set hash='"+hash+"' where id="+id+" limit 1" )
# commit every 200 queries
commit = 'no'
if (i%200==0):
i = 0
commit = 'yes'
# db2 is a second instance of db connexion
# home made db connexion class
# query function takes two parameters: query, boolean for commit
db2.query(updQuery,commit)
row = db.fetchone()
if commit == 'no':
db2.cnx.commit()
if j < limit2:
stillgoing = False
else:
limit1 += limit2
目前脚本需要 1 小时 30 到 2 小时才能完成 运行。这些是自脚本的第一个版本以来我获得的更好的表演。我能做些什么来让它 运行 更快吗?
我认为你应该能够完全在 MySQL:
内完成此操作
updateProductsQuery = "
UPDATE hashtable AS h
JOIN p ON h.id = p.id
LEFT JOIN att1 on p.id = att1.attid and att1.attrkey = 'PARAM'
LEFT JOIN att2 on p.id = att2.attid and att2.attrkey = 'NODE'
LEFT JOIN u on p.id = u.umid and u.lang = 'EN'
SET h.hash = MD5(CONCAT_WS('.', p.desc, p.macode, p.manuf, u.unit, p.weight, p.number, att1.attr, p.vcode, att2.attr))
LIMIT " + str(limit1) + ", " + str(limit2)
... LIMIT 0,200 -- touches 200 rows
... LIMIT 200,200 -- touches 400 rows
... LIMIT 400,200 -- touches 600 rows
... LIMIT 600,200 -- touches 800 rows
...
拿到照片了吗? LIMIT + OFFSET 是 O(N*N)。二次方慢。
要将其降低到 O(N),您需要进行一次 线性 扫描。如果单个查询(没有 LIMIT/OFFSET)花费的时间太长,则遍历 'chunks' 中的 table:
... WHERE id BETWEEN 1 AND 200 -- 200 rows
... WHERE id BETWEEN 201 AND 400 -- 200 rows
... WHERE id BETWEEN 401 AND 600 -- 200 rows
... WHERE id BETWEEN 601 AND 800 -- 200 rows
我在博客上写了这样的 here。如果您正在更新的 table 是 InnoDB 并且有 PRIMARY KEY(id)
,那么按 id
分块是非常有效的。
你可以 autocommit=1
这样每个 200 行 UPDATE
自动 COMMITs
.
哦,你的table用的是古董引擎MyISAM?好吧,它会 运行 相当好。
我正在编写一个脚本,该脚本创建一些数据的散列并将其保存在数据库中。所需的数据来自 SQL 查询,该查询将大约 30 万行与 50 万行连接起来。在解析结果时,我使用第二个连接处理程序创建哈希值并在数据库中更新(使用第一个连接处理程序给我一个 "Unread results" 错误)。
经过大量调查,我发现在性能方面给我最好的结果如下:
- 每 x 次迭代重新启动 select 查询。否则一段时间后更新会变慢
- 每 200 个查询才提交一次,而不是每次查询都提交一次
- 用于 select 查询的表是 MyISAM,并使用连接中使用的主键和字段进行索引。
- 我的散列 table 是 InnoDB,只有主键 (id) 被索引。
这是我的脚本:
commit = ''
stillgoing = True
limit1 = 0
limit2 = 50000
i = 0
while stillgoing:
j = 0
# rerun select query every 50000 results
getProductsQuery = ("SELECT distinct(p.id), p.desc, p.macode, p.manuf, "
"u.unit, p.weight, p.number, att1.attr as attribute1, p.vcode, att2.attr as attribute2 "
"FROM p "
"LEFT JOIN att1 on p.id = att1.attid and att1.attrkey = 'PARAM' "
"LEFT JOIN att2 on p.id = att2.attid and att2.attrkey = 'NODE' "
"LEFT JOIN u on p.id = u.umid and u.lang = 'EN' "
"limit "+str(limit1)+", "+str(limit2))
db.query(getProductsQuery)
row = db.fetchone()
while row is not None:
i += 1
j += 1
id = str(row[0])
# create hash value
to_hash = '.'.join( [ helper.tostr(s) for s in row[1:]] )
hash = hashlib.md5(to_hash.encode('utf-8')).hexdigest()
# set query
updQuery = ("update hashtable set hash='"+hash+"' where id="+id+" limit 1" )
# commit every 200 queries
commit = 'no'
if (i%200==0):
i = 0
commit = 'yes'
# db2 is a second instance of db connexion
# home made db connexion class
# query function takes two parameters: query, boolean for commit
db2.query(updQuery,commit)
row = db.fetchone()
if commit == 'no':
db2.cnx.commit()
if j < limit2:
stillgoing = False
else:
limit1 += limit2
目前脚本需要 1 小时 30 到 2 小时才能完成 运行。这些是自脚本的第一个版本以来我获得的更好的表演。我能做些什么来让它 运行 更快吗?
我认为你应该能够完全在 MySQL:
内完成此操作updateProductsQuery = "
UPDATE hashtable AS h
JOIN p ON h.id = p.id
LEFT JOIN att1 on p.id = att1.attid and att1.attrkey = 'PARAM'
LEFT JOIN att2 on p.id = att2.attid and att2.attrkey = 'NODE'
LEFT JOIN u on p.id = u.umid and u.lang = 'EN'
SET h.hash = MD5(CONCAT_WS('.', p.desc, p.macode, p.manuf, u.unit, p.weight, p.number, att1.attr, p.vcode, att2.attr))
LIMIT " + str(limit1) + ", " + str(limit2)
... LIMIT 0,200 -- touches 200 rows
... LIMIT 200,200 -- touches 400 rows
... LIMIT 400,200 -- touches 600 rows
... LIMIT 600,200 -- touches 800 rows
...
拿到照片了吗? LIMIT + OFFSET 是 O(N*N)。二次方慢。
要将其降低到 O(N),您需要进行一次 线性 扫描。如果单个查询(没有 LIMIT/OFFSET)花费的时间太长,则遍历 'chunks' 中的 table:
... WHERE id BETWEEN 1 AND 200 -- 200 rows
... WHERE id BETWEEN 201 AND 400 -- 200 rows
... WHERE id BETWEEN 401 AND 600 -- 200 rows
... WHERE id BETWEEN 601 AND 800 -- 200 rows
我在博客上写了这样的 here。如果您正在更新的 table 是 InnoDB 并且有 PRIMARY KEY(id)
,那么按 id
分块是非常有效的。
你可以 autocommit=1
这样每个 200 行 UPDATE
自动 COMMITs
.
哦,你的table用的是古董引擎MyISAM?好吧,它会 运行 相当好。