如何插入或更新大量行(关于表的 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_TIMESTAMP
。 last_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
列设置为无符号 INT
或 BIGINT
并忽略间隙。这里的问题是我害怕在几年更新后达到最大值。经过两天的更新,对于大约 3,000,000 个列表,我的 auto_increment 值已经达到了大约 12,000,000...
选项 B: 切换到 INSERT IGNORE ...
语句,检查受影响的行并在必要时检查 UPDATE ...
。
选项 C: SELECT ...
现有列表,检查是否存在于 python 和 INSERT ...
或 UPDATE ...
取决于。
还有其他明智的选择吗?
附加信息:我需要一个id
来获取与存储在其他表中的listing
相关的信息(例如listings_images
、listings_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
tuple
或 list
中,并且在抓取时,只抓取那些不在列表中的内容 - 失败,因为在将 url 加载到 Python tuple
或 list
脚本时消耗这么多服务器的内存
输入前检查每条记录 - 失败,因为它使 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_prices
、listings_images
等)。我会将此信息添加到我的原始帖子中。
Ok 保持自增列不变,然后在 url 列上添加唯一索引,然后查看 INSERT IGNORE 是否有效
之前试过了,但是更新丢失了。需要更新以检查ˋlistingˋ是否仍在线。我不是在抓取每个单独的 URL,只是在上面有多个列表的搜索结果页面。以上是关于如何插入或更新大量行(关于表的 auto_increment 值)的主要内容,如果未能解决你的问题,请参考以下文章
关于数据库更新/插入速率限制的一些查询(基于 SQL 或基于 NoSQL)
更新 MySQL 数据库中所有表的 AUTO_INCREMENT 值