【问题标题】:MYSQL Batch insert but assign unique personalized labelMYSQL 批量插入但分配唯一的个性化标签
【发布时间】:2021-04-15 10:26:06
【问题描述】:

我有一个相当独特的需求,我已经尝试解决好几个星期了。我有一个解决方案,但我认为它不够强大。

问题

我需要为使用 CSV 文件上传的项目生成唯一的个性化 唯一 标签。随着时间的推移,这些项目可能会增长到数十亿,并且通常会在 60/90 天后过期。因此,随着时间的推移,我添加的记录越多,我也会删除它们。删除很重要,因为我想确保给出一个长度最短的标签。

例如。假设我需要为以下项目分配标签。

department = 'A'
items = [ 1 => foo, 2 => bar, 3 => nik, 4 => mik, 5 => baz, 6 => nik, 7 => baz, 8 => baz, 9 => baz, 10 => naz]

department = 'B'
items = [ 1 => foo, 2 => bar, 3 => nik, 4 => mik, 5 => baz, 6 => nik, 7 => baz, 8 => baz, 9 => baz, 10 => naz]

插入这些记录后,我需要输出如下。

部门

id name
1 A
2 B

项目

id name label segment_value department_id
1 foo foo 0 1
2 bar bar 0 1
3 nik nik 0 1
4 mik mik 0 1
5 baz baz 0 1
6 nik nik1 1 1
7 baz baz1 1 1
8 baz baz2 2 1
9 baz baz3 3 1
11 foo foo 0 2
12 bar bar 0 2
13 nik nik 0 2
14 mik mik 0 2
15 baz baz 0 2
16 nik nik1 1 2
17 baz baz1 1 2
18 baz baz2 2 2
19 baz baz3 3 2
20 naz naz 0 2

我想要这种行为的原因是因为我需要一种方法来反向查找带有这样的参数的项目。 A/baz3 应该能够映射 items 表上的第 9 项。

当前解决方案

现在我有一个触发器,它在插入记录后更新标签。我维护一个外部计数器,每次插入触发器都会使用该信息来更新标签。

限制/当前问题

我每批插入这些项目 100,000 条记录或 200,000 条记录。所以我想要一个有效的插入。此外,我希望标签具有最小数值(如果我可以在 60 天内删除 baz1 后重新使用它会更好)。此外,我当前的触发器没有为每个 department_id 使用新的 segment_value

CREATE TRIGGER insert_items
            BEFORE INSERT ON items
                FOR EACH ROW BEGIN
                   SET NEW.segment_value = ((SELECT IFNULL(MAX(segment_value), 0) from purl_links where segment_key = NEW.segment_key) + 1);
                   SET NEW.label = CONCAT(NEW.label,NEW.segment_value);

                END

我正在寻找一个体面的解决方案,它应该可以帮助我避免冲突,并且能够批量插入并生成尽可能少的数字标签(如果可能,使用可用的旧插槽)。

PS :我试图以最好的方式抽象出我的问题,我正在寻求任何可以得到的帮助。

【问题讨论】:

  • 我不确定我是否完全理解这个要求,但你可能想看看UUID_SHORT
  • 它必须是一串数字吗? name(前缀)有多长? “最小长度”是指如果前 10 个数字可用,则必须使用 1 个数字?
  • 您是否需要在导入的同一事务中进行标记?即,您可以在导入批次后重新标记您的项目吗?
  • @RickJames 我的意思是最低限度,我对字符串部分没有限制,我的意思是它将在上传中修复。我将添加数字(并且需要确保它的长度非常短),同时使其独一无二。
  • @timur 不,我可以重新标记。但我还需要在导入后生成一个 csv。因此,在处理完 csv 后无法更改记录。我想我可以在标记后生成 csv。

标签: mysql algorithm mysql-8.0


【解决方案1】:

我建议不要在加载批次时尝试执行编号任务,而是建议如下:

  1. 将批处理加载到临时表中。
  2. 为结果字符串增加一列,可能还有一些索引。
  3. 对带有 一些 字符串的表运行查询 UPDATE -- 这可能涉及在临时表和实际表之间的 LEFT JOIN,以查看哪些可用,哪些不可用.
  4. 运行另一个查询以更新更多信息。

在第 3 步附近的某个地方,您可能会创建另一个表来发现是否所有的 1 位后缀都已用完;将结果存储在另一个临时表中。这会告诉您哪些前缀需要 2 位后缀。

可能有十几个这样的过程才能完成所有工作,但它只是十几个查询,没有触发器,没有循环(希望如此)等等。

但是,有一个问题...更新 100K 行非常慢,因为在崩溃的情况下会保存一些内容以供撤消。所以可能一次循环遍历 100K 行 1K 会更好。

即使在清除之后,您是否期望表中有“数十亿”或行?如果是这样,那么净化将是一个重大问题。为此,我推荐PARTITION BY RANGE(TO_DAYS(...))DROP PARTITION 比巨大的 DELETE 更快且侵入性更小。分区将是每周或每天。希望清除的时间不要太严格,因为某些字符串会在最后期限后持续数小时,从而阻止它们的重用。更多:http://mysql.rjweb.org/doc.php/partitionmaint

如果您不需要对“最小长度”过于严格,但愿意使用“尽力而为”。有了这个,你可以,例如,看到一个前缀当前正在使用 2 位后缀,尝试一个随机数或其他东西。如果随机数失败一次,下一次再试一次。

【讨论】:

    【解决方案2】:

    另一种方法是跟踪数字。毕竟,它们似乎很规律。例如:

    假设前缀 'baz' 当前使用了 1 到 3。你有另一个表

    prefix start end num_digits
    'naz'  1     3   1
    

    明天你加 4 和 5。1,5,1

    最终你点击 9、10、11 并且必须扩展为 2 位数字:1,11,2

    假设您到达1,76,2,是时候进行清除了。所以它变成4,76,2

    这有点棘手:您应该重复使用 1、2、3 吗?还是继续 77、78 等?好吧,您需要继续 77...99 才能回到起点。这里变得乱七八糟。 (我看到了几个选择。)让我们跑到算法的另一部分。

    由于有 100K+ 项可供使用,因此首先对列表进行排序以找出每个前缀需要多少个数字。 (想想COUNT(*)GROUP BY

    然后遍历批次,一次一个前缀。这部分算法最好用应用程序代码完成,而不是 SQL。这使您可以确定要使用的数字,并使每个 UPDATE 的大小易于管理。

    【讨论】:

      【解决方案3】:

      我建议您简单地继续添加随机数字,直到获得独特的数字。毕竟baz9 是否出现在baz1 之前并不重要。即使名称 baz 出现了数百次,您平均只需尝试 4 次即可找到可以使用的新标签。所以它在实践中很快。

      当然,您有时会得到一个额外的数字。但是您很少需要 2。而且您避免了完美解决方案所需的任何复杂(因此很慢)的逻辑。

      (我过去曾在一个类似的问题上使用过这种精确的技术,其中出现了很多重复的名字,并且看到了巨大的速度提升。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多