MariaDB/MySQL UPDATE 语句具有多个连接,包括范围连接

Posted

技术标签:

【中文标题】MariaDB/MySQL UPDATE 语句具有多个连接,包括范围连接【英文标题】:MariaDB/MySQL UPDATE statement with multiple joins including a ranged join 【发布时间】:2020-11-27 09:35:29 【问题描述】:

我有桌子

登录历史

create table login_history
(
    id         int auto_increment primary key,
    ip         int unsigned,
    created    datetime(6)  not null,
    uid    int unsigned not null,
);

IP 到位置表

create table ip2location
(
    ip_from      int unsigned not null primary key,
    ip_to        int unsigned null,
    country_code char(2)      null,
)

帐户表

create table account
(
    uid               int unsigned not null primary key,
);

一些订单

create table order
(
    id             int auto_increment primary key,
    item_id        varchar(20)       not null,
    price          int               not null,
    timestamp      datetime(6)       not null,
    country_code   char(2)           null,
    uid            int unsigned      null
)

所有表都有适合这个问题的索引。

我的目标是用 ip2location 表中的国家/地区填写订单的国家/地区代码。我有登录历史记录,因为我想让问题不再复杂,所以我可以使用最新的 IP,用户在给定的时间范围内。我假设在时间范围内切换国家并购买东西是一个可以忽略不计的用例。另外,由于登录历史只保留了几天,我想填写将 country_code 设置为 null 的旧订单,同时为用户获取国家/地区。

我的方法如下。

我正在尝试使用以下“on”表达式连接两个表。

update order

left join account using(uid)
left join (
    select uid, 
           LAST_VALUE(ip) over (PARTITION BY uid) as `ip_int`
    from login_history
    where created >= ' current_date '
    and created < ' next_date '
    group by user_id
    ) as lh
on account.uid = lh.uid
left join ip2location as ip on
    (ip.ip_from < login_history.ip_int and ip.ip_to >= login_history.ip_int)
    or
    (ip.ip_from = lh.ip_int)
set
    order.country_id = ip.country_id
where order.country_id is null;

它可以工作,但速度很慢。可能也是因为表格的大小:

login_history > 15 Mio。条目(where 语句将其减少到 500K 条目) 帐户 > 7 Mio。条目 ip2location ~ 200K 条目 订单 > 1 Mio。

也许这是 MariaDB 无法提供解决方案的用例。目标是在 30 秒内完成此查询。由于不锁表太久,当然越快越好。

我在以下陈述中看到了一些潜力。为了在 ip2location 表中找到正确的条目,我必须使用一个范围,并且我还必须考虑存在一个条目,其中只给出一个 IP,并且 ip_to 字段为空。

left join ip2location as ip on
        (ip.ip_from <= login_history.ip_int and ip.ip_to >= login_history.ip_int)
        or
        (ip.ip_from = lh.ip_int)

此外,以下选择看起来有些时间紧迫:

select uid, 
               LAST_VALUE(ip) over (PARTITION BY uid) as `ip_int`
        from login_history
        where created >= ' current_date '
        and created < ' next_date '
        group by user_id

我曾考虑通过先使用 select 然后使用 update 语句来拆分它,但最终,由于组织此任务的脚本,这可能会花费更多时间并且还会使用更多 CPU 时间。

您能帮我找到更好的查询吗?或者您对如何有效地解决这个问题有什么好的建议吗?

提前致谢,祝您有美好的一天!

【问题讨论】:

您可以尝试使用此 SO 问题的答案中描述的一些更奇特的技术来获取每个用户的最新登录信息:***.com/questions/7745609/… 您还需要了解您现有的任何索引是否有帮助您使用此查询 - 尽管在如此大的表上添加索引可能不是正确的方法。 【参考方案1】:

我认为以下基于相关子查询的方法可以满足您的要求:

update orders o
set country = (
    select il.country_code
    from login_history lh
    inner join ip2location il on lh.ip >= il.ip_from and lh.ip_to < il.ip_to
    where lh.created <= o.timestamp and lh.uid = o.uid
    order by lh.created desc limit 1
) 
where o.country_id is null

这会搜索日期早于或等于订单时间戳的同一用户的最新登录历史记录,并恢复相应的国家/地区。

【讨论】:

以上是关于MariaDB/MySQL UPDATE 语句具有多个连接,包括范围连接的主要内容,如果未能解决你的问题,请参考以下文章

(MariaDB/MySQL)MyISAM存储引擎读写操作的优先级

如何在 MariaDB/MySQL 工作台中获取 SELECT 语句的输出,以“table.column”格式命名列,而不仅仅是“column”?

PostgreSQL 中具有多个连接的 UPDATE 语句

SQL 错误:“嵌套的 INSERT、UPDATE、DELETE 或 MERGE 语句必须具有 OUTPUT 子句。” - 在 Azure Databricks 中执行时

比较所有列的 MariaDB/MySQL 的替代方案

使用 MariaDB/Mysql 进行多从属的多主复制