如何插入或更新大量行(关于表的 auto_increment 值)

Posted

技术标签:

【中文标题】如何插入或更新大量行(关于表的 auto_increment 值)【英文标题】:How to INSERT or UPDATE a large number of rows (regarding the auto_increment value of a table) 【发布时间】:2019-05-17 00:13:42 【问题描述】:

目前我有一个大约 300 万行 (listings) 的 mysql 表。这些列表由 python 脚本 (Scrapy) 使用 pymsql 更新 24/7(大约 30 个列表/秒) - 因此查询的性能是相关的!

如果listing 不存在(即UNIQUE url),将插入一条新记录(大约每百分之一列表)。 id 设置为 auto_increment,我使用的是 INSERT INTO listings ... ON DUPLICATE KEY UPDATE last_seen_at = CURRENT_TIMESTAMPlast_seen_at 上的更新对于检查该项目是否仍然在线是必要的,因为我正在抓取包含多个列表的搜索结果页面,而不是每次都检查每个单独的 URL。

+--------------+-------------------+-----+----------------+
| Field        | Type              | Key | Extra          |
+--------------+-------------------+-----+----------------+
| id           | int(11) unsigned  | PRI | auto_increment |
| url          | varchar(255)      | UNI |                |
| ...          | ...               |     |                |
| last_seen_at | timestamp         |     |                |
| ...          | ...               |     |                |
+--------------+-------------------+-----+----------------+

问题:

一开始,一切都很顺利。然后我注意到 auto_incremented id 列中的差距越来越大,并发现这是由于INSERT INTO ... 语句:MySQL 尝试先进行插入。这是id 自动递增的时候。一旦增加,它就会保持不变。然后检测到重复并进行更新。

现在我的问题是:从长远的角度来看,关于性能的最佳解决方案是什么?

选项 A:id 列设置为无符号 INTBIGINT 并忽略间隙。这里的问题是我害怕在几年更新后达到最大值。经过两天的更新,对于大约 3,000,000 个列表,我的 auto_increment 值已经达到了大约 12,000,000...

选项 B: 切换到 INSERT IGNORE ... 语句,检查受影响的行并在必要时检查 UPDATE ...

选项 C: SELECT ... 现有列表,检查是否存在于 python 和 INSERT ...UPDATE ... 取决于。

还有其他明智的选择吗?


附加信息:我需要一个id 来获取与存储在其他表中的listing 相关的信息(例如listings_imageslistings_prices 等)。恕我直言,使用 URL(这是唯一的)不是外键的最佳选择。

+------------+-------------------+
| Field      | Type              |
+------------+-------------------+
| listing_id | int(11) unsigned  |
| price      | int(9)            |
| created_at | timestamp         |
+------------+-------------------+

【问题讨论】:

第四个选项可能是删除 auto_increment id 列。 @P.Salmon 是的,这就是我在回答中建议的,不需要 ID 列,只需将 URL 设为 PRIMARY KEY 并在其上添加唯一索引 @P.Salmon 我添加了信息,有更多的表存储有关列表的信息,因此我需要一个 id - 使用长字符串 URL 不是最好的选择这个案例。纠正我,如果我错了。 【参考方案1】:

我和你的情况完全一样

我有数百万条记录被刮板输入到表中,刮板每天都在运行

我尝试关注但失败了

    将所有 url 加载到 Python tuplelist 中,并且在抓取时,只抓取那些不在列表中的内容 - 失败,因为在将 url 加载到 Python tuplelist 脚本时消耗这么多服务器的内存 输入前检查每条记录 - 失败,因为它使 INSERTion 过程太慢,因为它首先必须查询具有数百万行的表,然后决定是否插入

解决方案适用于我:(适用于具有数百万行的表)

    我删除了 id 列,因为它是不敬的,我不需要它 制作url PRIMARY KEY,因为它是唯一的 添加UNIQUE INDEX - 这是必须要做的 - 这将大大提高您的表的性能 进行批量插入而不是一个接一个地插入(参见下面的管道代码)

注意它使用的是INSERT IGNORE INTO,所以只会输入新记录,如果存在则完全忽略

如果你在MySQL中使用REPLACE INTO而不是INSERT IGNORE INTO,则会输入新记录,但如果存在记录,则会更新记录

class BatchInsertPipeline(object):

    def __init__(self):
        self.items = []
        self.query = None

    def process_item(self, item, spider):
        table = item['_table_name']
        del item['_table_name']

        if self.query is None:
            placeholders = ', '.join(['%s'] * len(item))
            columns = '`' + '`, `'.join(item.keys()).rstrip(' `') + '`'
            self.query = 'INSERT IGNORE INTO '+table+' ( %s ) VALUES ( %s )' \
                % (columns, placeholders)

        self.items.append(tuple(item.values()))

        if len(self.items) >= 500:
            self.insert_current_items(spider)   
        return item

    def insert_current_items(self,spider):
        spider.cursor.executemany(self.query, self.items)
        self.items = []


    def close_spider(self, spider):
        self.insert_current_items(spider)
        self.items = []

【讨论】:

感谢您的分享!这也是我的第一个想法——但我需要一个关系表的 ID,它存储listing 的更多信息(如listings_priceslistings_images 等)。我会将此信息添加到我的原始帖子中。 Ok 保持自增列不变,然后在 url 列上添加唯一索引,然后查看 INSERT IGNORE 是否有效 之前试过了,但是更新丢失了。需要更新以检查ˋlistingˋ是否仍在线。我不是在抓取每个单独的 URL,只是在上面有多个列表的搜索结果页面。

以上是关于如何插入或更新大量行(关于表的 auto_increment 值)的主要内容,如果未能解决你的问题,请参考以下文章

关于数据库更新/插入速率限制的一些查询(基于 SQL 或基于 NoSQL)

获取任何表的当前 AUTO_INCREMENT 值

如何创建表的回滚副本以防我插入或更新错误

更新 MySQL 数据库中所有表的 AUTO_INCREMENT 值

如何在 Debian 上更改 InnoDB AUTO_INCREMENT 锁定模式?

如何在 MySQL 中获取特定表的主键“列名”