MySQL线上死锁案例分析

项目场景

项目开发中有两张表:c_bill(账单表),c_bill_detail(账单明细表),他们的表结构如下(这里只保留必要信息):

CREATE TABLE `c_bill_detail` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `bill_detail_no` varchar(32)  NOT NULL DEFAULT '' COMMENT '对账单编号',
  `receivable_date` datetime(3) DEFAULT NULL COMMENT '应收日期',
  `order_type` varchar(20) NOT NULL DEFAULT '' COMMENT 
  `bill_no` varchar(32)  NOT NULL DEFAULT '' COMMENT '账单编号',
  `invoice_amount` decimal(12,4) NOT NULL COMMENT '开票金额',
  `active` tinyint NOT NULL DEFAULT '1' COMMENT '是否逻辑删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_bill_no` (`bill_no`) USING BTREE
) ENGINE=InnoDB COMMENT='客户账单明细';

CREATE TABLE `c_bill` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `bill_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '对账单编号',
  `should_receive_amount` decimal(12,4) NOT NULL COMMENT '应收总额',
  `actual_should_receive_amount` decimal(12,4) NOT NULL COMMENT '实际应收金额',
  `invoice_status` tinyint DEFAULT NULL COMMENT '开票状态(字典:invoice-status)',
  `invoice_amount` decimal(12,4) NOT NULL COMMENT '开票金额',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_bill_no` (`bill_no`) USING BTREE
) ENGINE=InnoDB COMMENT='客户账单';

c_bill表跟c_bill_detail表是1对多的关系,c_bill表中的invoice_amount是由c_bill_detail表中的invoice_amount统计出来的。
统计sql如下:

UPDATE c_bill
      SET invoice_amount = (SELECT ifnull(sum(invoice_amount), 0)
                            FROM c_bill_detail
                            WHERE bill_no = #{billNo}
                              AND active = 1
                              AND order_type in ('sale_order', 'supplement_order', 'subject_sale_order')),
          invoice_date   = #{invoiceDate},
          invoice_status =
              CASE
                  WHEN invoice_amount = should_receive_amount THEN 1
                  WHEN invoice_amount = 0 THEN 0
                  ELSE 2
                  END
      where bill_no = #{billNo}
        and active = 1;

业务层面,账单进行开发票操作后,会更新c_bill_detail表跟c_bill

问题描述

有一天线上出现告警:
在这里插入图片描述
从日志上看发生了死锁,通过定位代码发现跟执行以下sql有关:

UPDATE c_bill
      SET invoice_amount = (SELECT ifnull(sum(invoice_amount), 0)
                            FROM c_bill_detail
                            WHERE bill_no = #{billNo}
                              AND active = 1
                              AND order_type in ('sale_order', 'supplement_order', 'subject_sale_order')),
          invoice_date   = #{invoiceDate},
          invoice_status =
              CASE
                  WHEN invoice_amount = should_receive_amount THEN 1
                  WHEN invoice_amount = 0 THEN 0
                  ELSE 2
                  END
      where bill_no = #{billNo}
        and active = 1;

通过数据库锁分析得到如下信息:
在这里插入图片描述
从上面信息可以得到以下信息:

  • 事务1等待c_bill_detail表的S锁,该锁对应的索引名称是 PRIMARY(也就是主键索引,id)
  • 事务1持有c_bill表的X锁,该锁对应的索引名称是uk_bill_no
  • 事务2等待c_bill表的X锁,该锁对应的索引名称为uk_bill_no
  • 事务2持有c_bill_detail表额S锁,该锁对应的索引名称是PRIMARY(也就是主键索引,id)

通过上面可以看出,事务1跟事务2直接的锁进入了死循环,形成了死锁。

原因分析:

死锁数据分析

上面的途中,给出了死锁有关的两个索引:c_bill_detail表的主键索引,跟c_bill表的主键索引。c_bill表知道是执行了上面提到的统计sql,那么,c_bill_detail表是执行了什么操作呢?

首先通过审计找出当时这两个事务的操作:
在这里插入图片描述
上面是线程3213915(事务A)的有关操作,可以看到对c_bill_detail表有如下更新:

-- SQL
UPDATE
  c_bill_detail
SET
  receivable_date = '2023-11-15 00:00:00',
  invoice_status = 2,
  invoice_amount = 305412,
  updater = '管理员',
  updater_code = 'ADMINISTRATOR',
  update_time = '2023-12-08 17:47:52.382000'
WHERE id = 146947
  AND active = 1

线程3213754(事务B)操作如下
在这里插入图片描述

UPDATE
  c_bill_detail
SET
  receivable_date = '2023-11-15 00:00:00',
  invoice_status = 2,
  invoice_amount = 305412,
  updater = '管理员',
  updater_code = 'ADMINISTRATOR',
  update_time = '2023-12-08 17:47:52.381000'
WHERE id = 147471
  AND active = 1;

从上面可以看出事务A对表c_bill_detailid = 146947数据进行了更新,事务B对表c_billid=147471进行了更新。

通过审计日志还发现,事务A跟事务B也都更新了c_bill表,而且都是更新了bill_no=XSZD202309070005这一行数据。

事务A:
在这里插入图片描述

UPDATE
  c_bill
SET
  invoice_amount =
  (SELECT
    IFNULL(SUM(invoice_amount), 0)
  FROM
    c_bill_detail
  WHERE bill_no = 'XSZD202309070005'
    AND active = 1
    AND order_type IN (
      'sale_order',
      'supplement_order',
      'subject_sale_order'
    )),
  invoice_date = '2023-12-08 00:00:00',
  invoice_status =
  CASE
    WHEN invoice_amount = should_receive_amount
    THEN 1
    WHEN invoice_amount = 0
    THEN 0
    ELSE 2
  END
WHERE bill_no = 'XSZD202309070005'
  AND active = 1;

事务B:

在这里插入图片描述

-- SQL
UPDATE
  c_bill
SET
  invoice_amount =
  (SELECT
    IFNULL(SUM(invoice_amount), 0)
  FROM
    c_bill_detail
  WHERE bill_no = 'XSZD202309070005'
    AND active = 1
    AND order_type IN (
      'sale_order',
      'supplement_order',
      'subject_sale_order'
    )),
  invoice_date = '2023-12-08 00:00:00',
  invoice_status =
  CASE
    WHEN invoice_amount = should_receive_amount
    THEN 1
    WHEN invoice_amount = 0
    THEN 0
    ELSE 2
  END
WHERE bill_no = 'XSZD202309070005'
  AND active = 1;

上图可以看出,事务B最终在更新c_bill时失败回滚了(因为发生了死锁)。

通过查看数据发现,c_bill_detailid = 146947id=147471对应的bill_no都是XSZD202309070005
在这里插入图片描述
到这里,只是发现了数据上的关联,还是不知道为什么会出现死锁,下面在其他环境进行复现。

select语句添加了共享读锁

为了更好复现这个死锁情况,现将线上的sql执行顺序整理如下:

在这里插入图片描述

下面在本地数据库,选取c_bill_detailid=19380id=19381,这两条数据有相同的bill_no=XSZD202211226768

在这里插入图片描述
开启两个事务,分别按照上面表格的sql数据进行执行,同时观察锁情况:

事务A 更新id = 19380:

Database changed
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE
    ->   c_bill_detail
    -> SET
    ->   receivable_date = '2023-11-15 00:00:00',
    ->   invoice_status = 2,
    ->   invoice_amount = 305412,
    ->   updater = '管理员',
    ->   updater_code = 'ADMINISTRATOR',
    ->   update_time = '2023-12-08 17:47:52.382000'
    -> WHERE id = 19380
    ->   AND active = 1;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1  Changed: 1  Warnings: 0

事务B更新id = 19381的记录

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> UPDATE
    ->   c_bill_detail
    -> SET
    ->   receivable_date = '2023-11-15 00:00:00',
    ->   invoice_status = 2,
    ->   invoice_amount = 305412,
    ->   updater = '管理员',
    ->   updater_code = 'ADMINISTRATOR',
    ->   update_time = '2023-12-08 17:47:52.381000'
    -> WHERE id = 19381
    ->   AND active = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

这是观察锁情况

mysql> select * from performance_schema.data_locks\G;
*************************** 1. row ***************************
  // 省略表意向锁
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347952208:230:8:12:139645358792496
ENGINE_TRANSACTION_ID: 65810
            THREAD_ID: 563867
             EVENT_ID: 34
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill_detail
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 139645358792496
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 19381
*************************** 3. row ***************************
       // 省略表锁
*************************** 4. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347951400:230:8:9:139645358786480
ENGINE_TRANSACTION_ID: 65809
            THREAD_ID: 563866
             EVENT_ID: 34
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill_detail
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 139645358786480
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 19380
4 rows in set (0.00 sec)

从上面看出,c_bill_detail表的id=19381id=19380的数据加上了X锁,这是意料之中的。

接下来事务A执行更新c_bill

mysql> UPDATE
    ->   c_bill
    -> SET
    ->   invoice_amount =
    ->   (SELECT
    ->     IFNULL(SUM(invoice_amount), 0)
    ->   FROM
    ->     c_bill_detail
    ->   WHERE bill_no = 'XSZD202211226768'
    ->     AND active = 1
    ->     AND order_type IN (
    ->       'sale_order',
    ->       'supplement_order',
    ->       'subject_sale_order'
    ->     )),
    ->   invoice_date = '2023-12-08 00:00:00',
    ->   invoice_status =
    ->   CASE
    ->     WHEN invoice_amount = should_receive_amount
    ->     THEN 1
    ->     WHEN invoice_amount = 0
    ->     THEN 0
    ->     ELSE 2
    ->   END
    -> WHERE bill_no = 'XSZD202211226768'
    ->   AND active = 1;

此时事务A发生了阻塞
在这里插入图片描述

这时查看锁情况:

mysql> select * from performance_schema.data_locks\G;
*************************** 1. row ***************************
           // 表锁
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347952208:230:8:16:139645358792496
ENGINE_TRANSACTION_ID: 65820
            THREAD_ID: 563867
             EVENT_ID: 43
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill_detail
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 139645358792496
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 19381
*************************** 3. row ***************************
               //c_bill 表意向锁
*************************** 4. row ***************************
              // c_bill_detail表意向锁
*************************** 5. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347951400:230:8:15:139645358786480
ENGINE_TRANSACTION_ID: 65819
            THREAD_ID: 563866
             EVENT_ID: 44
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill_detail
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 139645358786480
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 19380
*************************** 6. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347951400:229:5:6:139645358786824
ENGINE_TRANSACTION_ID: 65819
            THREAD_ID: 563866
             EVENT_ID: 45
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: uk_bill_no
OBJECT_INSTANCE_BEGIN: 139645358786824
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 'XSZD202211226768', 5117
*************************** 7. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347951400:229:7:6:139645358787168
ENGINE_TRANSACTION_ID: 65819
            THREAD_ID: 563866
             EVENT_ID: 45
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 139645358787168
            LOCK_TYPE: RECORD
            LOCK_MODE: X,REC_NOT_GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 5117
*************************** 8. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347951400:230:58:117:139645358787512
ENGINE_TRANSACTION_ID: 65819
            THREAD_ID: 563866
             EVENT_ID: 45
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill_detail
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: idx_bill_no
OBJECT_INSTANCE_BEGIN: 139645358787512
            LOCK_TYPE: RECORD
            LOCK_MODE: S
          LOCK_STATUS: GRANTED
            LOCK_DATA: 'XSZD202211226768', 19380
*************************** 9. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347951400:230:58:118:139645358787512
ENGINE_TRANSACTION_ID: 65819
            THREAD_ID: 563866
             EVENT_ID: 45
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill_detail
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: idx_bill_no
OBJECT_INSTANCE_BEGIN: 139645358787512
            LOCK_TYPE: RECORD
            LOCK_MODE: S
          LOCK_STATUS: GRANTED
            LOCK_DATA: 'XSZD202211226768', 19381
*************************** 10. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 139645347951400:230:8:16:139645358787856
ENGINE_TRANSACTION_ID: 65819
            THREAD_ID: 563866
             EVENT_ID: 45
        OBJECT_SCHEMA: fresh
          OBJECT_NAME: c_bill_detail
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 139645358787856
            LOCK_TYPE: RECORD
            LOCK_MODE: S,REC_NOT_GAP
          LOCK_STATUS: WAITING
            LOCK_DATA: 19381
10 rows in set (0.00 sec)

从上面的Row10发现,事务A跟表c_bill_detail的id = 19381的记录添加了S锁,并且在锁状态为等待状态。

接着事务B也执行更新c_bill表,发现就会出现死锁的情况。
在这里插入图片描述
到这里总结上面的持锁过程:

  • 事务A先持有t_bill_detailid=19380X
  • 接着事务B持有t_bll_detailid=19381X锁,与上一把没有存在锁竞争,都能正常执行
  • 事务A更新c_bill,这时不仅给表c_bill表的bill_no=XSZD202211226768的记录加上了X锁,同时也给c_bill_detailid=19381的记录添加了S锁,并且处于等待状态。
  • 事务B更新c_bill同样会给c_bill_detailid=19380的记录添加S锁。

这里有一下几点需要注意:

  1. S锁跟X锁不兼容,会出现锁等待的情况。
  2. 普通的select语句是不加锁的,但是在update语句中进行select查询赋值,这时的select就会添加上共享锁。
  3. 共享锁主要是保证每次读取的都是最新的值(读取时不支持修改)。

以上就是生成环境形成死锁的分析过程。关于X锁跟S锁的更多说明,可以参考:Innodb中的锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/243195.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Gin之GORM 查询语句

前期工作可以看之前的(连接数据库;以及确定要操作的库) Gin之GORM 操作数据库(MySQL)-CSDN博客https://blog.csdn.net/m0_72264240/article/details/134948202?spm1001.2014.3001.5502这次我们操作gin库下的另外一个…

Lenovo联想拯救者Legion Y9000X 2021款(82BD)原装出厂Windows10系统

链接:https://pan.baidu.com/s/1GRTR7CAAQJdnh4tHbhQaDQ?pwdl42u 提取码:l42u 联想原厂WIN10系统自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具:16G或以上的U盘 文件格式&am…

记录汇川:套接字TCP通信-梯形图

H5U集成一路以太网接口。使用AutoShop可以通过以太网方便、快捷对H5U进行行监控、下载、上载以及调试等操作。同时也可以通过以太网与网络中的其他设备进行数据交互。H5U集成了Modbus-TCP协议,包括服务器与客户端。可轻松实现与支持Modbus-TCP的设备进行通讯与数据交…

Redis哨兵模式:什么是哨兵模式、哨兵模式的优缺点、哨兵模式的主观下线和客观下线、投票选举、Redis 哨兵模式搭建

文章目录 什么是哨兵模式哨兵模式的优缺点主观下线和客观下线投票选举哨兵模式场景应用Redis version 6.0.5 集群搭建下载文件环境安装解压编译配置文件启动关闭密码设置 什么是哨兵模式 哨兵模式是Redis的高可用解决方案之一,它旨在提供自动故障转移和故障检测的功…

数据分析基础之《numpy(3)—基本操作》

一、基本操作 1、adarray.方法() 2、np.函数名() 二、生成数组的方法 1、生成0和1的数组 为什么需要生成0和1的数组? 我们需要占用位置,或者生成一个空的数组 (1)ones(shape[, dtype, order]) 生成一组1 shape:形…

STM32读取EEPROM存储芯片AT24C512故障然后排坑记录

背景: 有一个项目用到STM32F091芯片去读取 AT24C512C-SSHD EEPROM 芯片,我直接移植了之前项目的IIC库,结果程序运行后,读不出EEPROM里面的数据。 摘要: 本文主要介绍一个基于STM32F091芯片和AT24C512C-SSHD EEPROM芯片…

Java面向对象思想以及原理以及内存图解

文章目录 什么是面向对象面向对象和面向过程区别创建一个对象用什么运算符?面向对象实现伪代码面向对象三大特征类和对象的关系。 基础案例代码实现实例化创建car对象时car引用的内存图对象调用方法过程 成员变量和局部变量作用范围在内存中的位置 关于对象的引用关系简介相关…

6、生产者压缩算法面面观

生产者压缩算法面面观 1、怎么压缩?2、何时压缩?2.1、生产者端2.2、Broker 端 3、何时解压缩?4、各种压缩算法对比 压缩的思想,实际就是用时间去换空间的经典 trade-off 思想,在 Kafka 中,就是用 CPU 时间去…

Linux | 多线程

前言 本文主要介绍多线程基础知识,以及使用多线程技术进行并发编程;最后会介绍生产者消费者模型; 一、线程基本认识 1、什么是线程 如果你是科班出生,你肯定听过线程相关概念;但是你可能没有真正搞懂什么是线程&#…

十八)Stable Diffusion使用教程:艺术二维码案例

今天说说怎么样使用SD生成艺术二维码。 我们直接上图。 方式有三种,分别如下: 1)方式一:直接 contronet 的tile模型进行控制 使用QRBTF Classic生成你的二维码。 首先输入网址,选择喜欢的二维码样式(推荐第一种就行): 然后选择相应参数,这里推荐最大的容错率,定…

Linux 安装图形界面 “startx”

———————————————— 报错,如下: bash :startx command not found ———————————————— 解决方法: 1.先安装 — X Windows System,输入以下命令: yum groupinstall “X Window System”…

第一个“hello Android”程序

1、首先安装Android studio(跳过) Android Studio是由Google推出的官方集成开发环境(IDE),专门用于Android应用程序的开发。它是基于JetBrains的IntelliJ IDEA IDE构建的,提供了丰富的功能和工具&#xff0…

2002-2023年各省环境规制力度数据(ZF报告词频环境规制关键词词频统计)

2002-2023年各省环境规制力度数据(ZF报告词频环境规制关键词词频统计) 1、时间:2001-2022年 2、指标:文本总长度、仅中英文-文本总长度、文本总词频-全模式、文本总词频-精确模式、环境规制力度词频和、环境保护、环保、污染、能…

Linux常用命令(二)

目录 Linux常用命令(二)1、grep命令2、df命令3、hostname命令4、ps命令5、top命令6、echo命令7、cal命令8、firewall-cmd命令9、du命令10、netstat命令 Linux常用命令(二) 1、grep命令 功能说明:查找文件里符合条件的字符串。 举 例:ps aux | grep yum…

高通平台开发系列讲解(SIM卡篇)SIM卡基础概念

文章目录 一、SIM卡基本定义二、卡的类型三、SIM卡的作用三、SIM卡基本硬件结构四、SIM卡的内部物理单元五、卡文件系统沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇文章将介绍SIM的相关组件。 一、SIM卡基本定义 功能和作用:SIM卡的主要功能是存储用户的身份信…

【Hadoop】Hadoop基础架构的变化

1.x版本架构2.x版本架构3.x版本架构参考 1.x版本架构 NameNode:,负责文件系统的名字空间(Namespace)管理以及客户端对文 件的访问。NameNode负责文件元数据的管理和操作。是单节点。 Secondary NameNode:它的职责是合并NameNode的edit logs到…

什么是自我力量?如何提高自我力量?

自我力量 ,是承受力和容纳力的评估指标,可以理解为不逃避,承受情感、冲动和幻想的能力,提高学习和工作效率。在企业人才测评中,ES用于评估工作能力,在校学生则可用于评估学习效率。 自我力量 ,…

【什么是POI,为什么它会导致内存溢出?】

什么是POI,为什么它会导致内存溢出 什么是POIExcel并没看到的那么小POI的溢出原理 拓展知识几种Workbook格式 什么是POI Apache POl,是一个非常流行的文档处理工具,通常大家会选择用它来处理Excel文件。但是在实际使用的时候经常会遇到内存溢…

关键点检测☞png格式换bmp,且labelme标注的json中imagePath同步修改格式

import os import cv2 import jsondef bmp2jpg(in_img_path, out_dir_name): # .png -> .bmp# img = cv2.imread(in_img_path) # 彩色图片,位深24img =</

【虹科分享】基于Redis Enterprise,LangChain,OpenAI 构建一个电子商务聊天机器人

如何构建你自己的商务聊天机器人&#xff1f;注意哦&#xff0c;是你自己的聊天机器人。一起来看看Redis Enterprise的向量检索是怎么帮你实现这个愿望的吧。 鉴于最近人工智能支持的API和网络开发工具的激增&#xff0c;似乎每个人都在将聊天机器人集成到他们的应用程序中。 …