如何在生产MySQL数据库中更新40万行而不导致崩溃

3 投票
3 回答
1301 浏览
提问于 2025-04-16 05:43

在我们的生产服务器上,我们需要把90万张图片分到不同的文件夹里,并更新40万条记录(使用的是MySQL的InnoDB引擎)。我写了一个Python脚本,按照以下步骤进行操作:

  1. 从数据库中选择一小部分数据(10条记录)
  2. 创建新的文件夹
  3. 把文件复制到新创建的文件夹里,并重命名
  4. 更新数据库(更新时会有一些触发器,这会增加服务器负担)
  5. 重复以上步骤

我的代码:


import os, shutil
import database # database.py from tornado

LIMIT_START_OFFSET = 0
LIMIT_ROW_COUNT = 10
SRC_PATHS = ('/var/www/site/public/upload/images/',)
DST_PATH = '/var/www/site/public/upload/new_images/'

def main():
    offset = LIMIT_START_OFFSET
    while True:
        db = Connection(DB_HOST, DB_NAME, DB_USER, DB_PASSWD)
        db_data = db.query('''
            SELECT id AS news_id, image AS src_filename
            FROM emd_news
            ORDER BY id ASC
            LIMIT %s, %s''', offset, LIMIT_ROW_COUNT)
        offset = offset + LIMIT_ROW_COUNT
        news_images = get_news_images(db_data) # convert data to easy-to-use list
        make_dst_dirs(DST_PATH, [i['dst_dirname'] for i in news_images]) # make news dirs
        news_to_update = copy_news_images(SRC_PATHS, DST_PATH, news_images) # list of moved files
        db.executemany('''
            UPDATE emd_news
            SET image = %s
            WHERE id = %s
            LIMIT 1''', [(i['filename'], i['news_id']) for i in news_to_update])
        db.close()
        if not db_data: break

if __name__ == '__main__':
    main()

这个任务其实挺简单的,但我对性能有点担心。

我该如何让这个脚本运行得更高效呢?

更新:最后我还是用了原来的脚本,没有做任何修改。整个过程大约花了5个小时。一开始速度很快,但到最后就变得很慢了。

3 个回答

1
当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。
2

我给你的建议:

  1. 在你的表格里加一个 isProcessed 列。
  2. 让你的脚本第一次运行时处理一小部分,比如说1000行(当然只选择那些还没处理的行)。
  3. 测试一下这个过程的速度。
  4. 如果需要,可以调整一下每次处理的行数。
  5. 再写一个脚本,定时调用这个脚本。

别忘了在你的两个脚本里加一些休眠时间哦!

这样做是可行的,前提是你的更改不需要一直进行(我觉得其实不需要)。如果你必须一次性处理完,建议在脚本运行的时候把数据库下线。

3

我想补充一下:

你为什么在每次循环中都创建一个新的连接,然后又把它关闭呢,哈哈!!!

另外,你可以试试用 db.autocommit(False),特别是在进行更新操作时,然后每处理100行数据就执行一次 db.commit();

还有像 Alin Purcaru 那样,你也应该做一些性能测试。

希望这些能帮到你 :)

撰写回答