【Apollo学习笔记】——规划模块TASK之LANE_CHANGE_DECIDER

文章目录

  • 前言
  • LANE_CHANGE_DECIDER功能简介
  • LANE_CHANGE_DECIDER相关配置
  • LANE_CHANGE_DECIDER总体流程
  • LANE_CHANGE_DECIDER相关子函数
    • PrioritizeChangeLane
    • UpdateStatus
    • IsClearToChangeLane
    • HysteresisFilter
  • 参考

前言

在Apollo星火计划学习笔记——Apollo路径规划算法原理与实践与【Apollo学习笔记】——Planning模块讲到……Stage::Process的PlanOnReferenceLine函数会依次调用task_list中的TASK,本文将会继续以LaneFollow为例依次介绍其中的TASK部分究竟做了哪些工作。由于个人能力所限,文章可能有纰漏的地方,还请批评斧正。

modules/planning/conf/scenario/lane_follow_config.pb.txt配置文件中,我们可以看到LaneFollow所需要执行的所有task。

stage_config: {stage_type: LANE_FOLLOW_DEFAULT_STAGEenabled: truetask_type: LANE_CHANGE_DECIDERtask_type: PATH_REUSE_DECIDERtask_type: PATH_LANE_BORROW_DECIDERtask_type: PATH_BOUNDS_DECIDERtask_type: PIECEWISE_JERK_PATH_OPTIMIZERtask_type: PATH_ASSESSMENT_DECIDERtask_type: PATH_DECIDERtask_type: RULE_BASED_STOP_DECIDERtask_type: SPEED_BOUNDS_PRIORI_DECIDERtask_type: SPEED_HEURISTIC_OPTIMIZERtask_type: SPEED_DECIDERtask_type: SPEED_BOUNDS_FINAL_DECIDERtask_type: PIECEWISE_JERK_SPEED_OPTIMIZER# task_type: PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZERtask_type: RSS_DECIDER

本文将从第一个task——LANE_CHANGE_DECIDER开始介绍。

LANE_CHANGE_DECIDER功能简介

在这里插入图片描述
LANE_CHANGE_DECIDER主要功能是:产生是否换道的决策,更新换道状态
主要逻辑是:首先判断是否产生多条参考线,若只有一条参考线,则保持直行。若有多条参考线,则根据一些条件(主车的前方和后方一定距离内是否有障碍物,旁边车道在一定距离内是否有障碍物)进行判断是否换道,当所有条件都满足时,则进行换道决策。

LANE_CHANGE_DECIDER相关配置

LANE_CHANGE_DECIDER的相关配置集中在以下两个文件:modules/planning/conf/planning_config.pb.txtmodules/planning/conf/scenario/lane_follow_config.pb.txt

// modules/planning/conf/planning_config.pb.txt
default_task_config: {task_type: LANE_CHANGE_DECIDERlane_change_decider_config {enable_lane_change_urgency_check: falseenable_prioritize_change_lane: falseenable_remove_change_lane: falsereckless_change_lane: falsechange_lane_success_freeze_time: 1.5change_lane_fail_freeze_time: 1.0}
}
// modules/planning/conf/scenario/lane_follow_config.pb.txttask_config: {task_type: LANE_CHANGE_DECIDERlane_change_decider_config {enable_lane_change_urgency_check: true}}

LANE_CHANGE_DECIDER总体流程

总体流程图如下所示:
在这里插入图片描述

接着来看一看LANE_CHANGE_DECIDER的整体代码,文件路径:modules/planning/tasks/deciders/lane_change_decider/lane_change_decider.cc
LANE_CHANGE_DECIDER实现逻辑在Process函数中:

// added a dummy parameter to enable this task in ExecuteTaskOnReferenceLine
Status LaneChangeDecider::Process(Frame* frame, ReferenceLineInfo* const current_reference_line_info) {// Sanity checks.CHECK_NOTNULL(frame);// 读取配置文件const auto& lane_change_decider_config = config_.lane_change_decider_config();// 读取ReferenceLineInfo,并检查是否为空std::list<ReferenceLineInfo>* reference_line_info =frame->mutable_reference_line_info();if (reference_line_info->empty()) {const std::string msg = "Reference lines empty.";AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}// 始终允许车辆变道。车辆可能持续变道 config_path:modules/planning/proto/task_config.protoif (lane_change_decider_config.reckless_change_lane()) {PrioritizeChangeLane(true, reference_line_info);return Status::OK();}// 获取上一时刻变道状态信息并记录时间戳auto* prev_status = injector_->planning_context()->mutable_planning_status()->mutable_change_lane();double now = Clock::NowInSeconds();// 判断当前参考线是否安全可用prev_status->set_is_clear_to_change_lane(false);if (current_reference_line_info->IsChangeLanePath()) {prev_status->set_is_clear_to_change_lane(IsClearToChangeLane(current_reference_line_info));}// 是否获取到状态信息if (!prev_status->has_status()) {UpdateStatus(now, ChangeLaneStatus::CHANGE_LANE_FINISHED,GetCurrentPathId(*reference_line_info));prev_status->set_last_succeed_timestamp(now);return Status::OK();}// 参考线的数目是否大于1// 根据reference line的数量判断是否处于变道场景中,size() > 1则处于变道过程中,需要判断变道的状态bool has_change_lane = reference_line_info->size() > 1;ADEBUG << "has_change_lane: " << has_change_lane;// 只有一条reference line,没有进行变道if (!has_change_lane) {// 根据当前唯一的reference line,获得当前道路lane的IDconst auto& path_id = reference_line_info->front().Lanes().Id();// 上一时刻是否变道完成if (prev_status->status() == ChangeLaneStatus::CHANGE_LANE_FINISHED) {// 上一时刻是否在变道中。若有,这一时刻只有一条reference line,说明变道成功} else if (prev_status->status() == ChangeLaneStatus::IN_CHANGE_LANE) {// 更新当前时刻,变道完成状态,以及当前道路的IDUpdateStatus(now, ChangeLaneStatus::CHANGE_LANE_FINISHED, path_id);// 上一时刻是否变道失败} else if (prev_status->status() == ChangeLaneStatus::CHANGE_LANE_FAILED) {} else {const std::string msg =absl::StrCat("Unknown state: ", prev_status->ShortDebugString());AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}// 返回LaneChangeDecider::Process 的状态为OKreturn Status::OK();} else {  // has change lane in reference lines.// 获取自车当前所在车道的IDauto current_path_id = GetCurrentPathId(*reference_line_info);// 如果当前所在车道为空,则返回error状态if (current_path_id.empty()) {const std::string msg = "The vehicle is not on any reference line";AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}// 如果上一时刻处在变道中,根据上一时刻自车所处道路ID与当前时刻所处道路ID对比,来确认变道状态if (prev_status->status() == ChangeLaneStatus::IN_CHANGE_LANE) {// ID相同则说明变道还在进行中,if (prev_status->path_id() == current_path_id) {// 同时调用PrioritizeChangeLane(),将目标车道的reference line放在首位PrioritizeChangeLane(true, reference_line_info);} else {// RemoveChangeLane(reference_line_info);// ID不同则说明变道已经完成,PrioritizeChangeLane(false, reference_line_info);ADEBUG << "removed change lane.";// 更新状态UpdateStatus(now, ChangeLaneStatus::CHANGE_LANE_FINISHED,current_path_id);}return Status::OK();// 上一时刻变道失败} else if (prev_status->status() == ChangeLaneStatus::CHANGE_LANE_FAILED) {// TODO(SHU): add an optimization_failure counter to enter// change_lane_failed status// 判断当前时刻减上一时刻的时间差是否小于换道失败冻结时间// not allowed to change lane this amount of time if just failedif (now - prev_status->timestamp() <lane_change_decider_config.change_lane_fail_freeze_time()) {// RemoveChangeLane(reference_line_info);PrioritizeChangeLane(false, reference_line_info);ADEBUG << "freezed after failed";} else {UpdateStatus(now, ChangeLaneStatus::IN_CHANGE_LANE, current_path_id);ADEBUG << "change lane again after failed";}return Status::OK();// 若上一时刻换道完成} else if (prev_status->status() ==ChangeLaneStatus::CHANGE_LANE_FINISHED) {// 判断当前时刻减上一时刻的时间差是否小于换道完成冻结时间if (now - prev_status->timestamp() <lane_change_decider_config.change_lane_success_freeze_time()) {// RemoveChangeLane(reference_line_info);PrioritizeChangeLane(false, reference_line_info);ADEBUG << "freezed after completed lane change";} else {PrioritizeChangeLane(true, reference_line_info);UpdateStatus(now, ChangeLaneStatus::IN_CHANGE_LANE, current_path_id);ADEBUG << "change lane again after success";}} else {const std::string msg =absl::StrCat("Unknown state: ", prev_status->ShortDebugString());AERROR << msg;return Status(ErrorCode::PLANNING_ERROR, msg);}}return Status::OK();
}

LANE_CHANGE_DECIDER相关子函数

PrioritizeChangeLane

// 提升变道的优先级,找到变道的参考线,并将其置于首位(is_prioritize_change_lane == true)
void LaneChangeDecider::PrioritizeChangeLane(const bool is_prioritize_change_lane,std::list<ReferenceLineInfo>* reference_line_info) const {if (reference_line_info->empty()) {AERROR << "Reference line info empty";return;}const auto& lane_change_decider_config = config_.lane_change_decider_config();// TODO(SHU): disable the reference line order change for nowif (!lane_change_decider_config.enable_prioritize_change_lane()) {return;}// 遍历reference_line_info列表中的元素,并检查当前元素是否为变道路径(IsChangeLanePath)// 找到第一个需要优先排序的元素后,循环会被中断// 0、is_prioritize_change_lane 根据参考线数量置位True 或 False// 1、如果is_prioritize_change_lane为True// 首先获取第一条参考线的迭代器,然后遍历所有的参考线,// 如果当前的参考线为允许变道参考线,则将第一条参考线更换为当前迭代器所指向的参考线,// 注意,可变车道为按迭代器的顺序求取,一旦发现可变车道,即推出循环。// // 2、如果is_prioritize_change_lane 为False,// 找到第一条不可变道的参考线,将第一条参考线更新为当前不可变道的参考线auto iter = reference_line_info->begin();while (iter != reference_line_info->end()) {ADEBUG << "iter->IsChangeLanePath(): " << iter->IsChangeLanePath();/* is_prioritize_change_lane == true: prioritize change_lane_reference_lineis_prioritize_change_lane == false: prioritizenon_change_lane_reference_line */if ((is_prioritize_change_lane && iter->IsChangeLanePath()) ||(!is_prioritize_change_lane && !iter->IsChangeLanePath())) {ADEBUG << "is_prioritize_change_lane: " << is_prioritize_change_lane;ADEBUG << "iter->IsChangeLanePath(): " << iter->IsChangeLanePath();break;}++iter;}// 将变道的参考线置于列表首位(is_prioritize_change_lane == true)reference_line_info->splice(reference_line_info->begin(),*reference_line_info, iter);ADEBUG << "reference_line_info->IsChangeLanePath(): "<< reference_line_info->begin()->IsChangeLanePath();
}

UpdateStatus

void LaneChangeDecider::UpdateStatus(double timestamp,ChangeLaneStatus::Status status_code,const std::string& path_id) {auto* lane_change_status = injector_->planning_context()->mutable_planning_status()->mutable_change_lane();lane_change_status->set_timestamp(timestamp);lane_change_status->set_path_id(path_id);lane_change_status->set_status(status_code);
}

IsClearToChangeLane

// 用于检查当前参考线是否安全,或者当前参考线是否可以偏离后返回
bool LaneChangeDecider::IsClearToChangeLane(ReferenceLineInfo* reference_line_info) {// 获得当前参考线自车的s坐标的起点与终点,以及自车线速度double ego_start_s = reference_line_info->AdcSlBoundary().start_s();double ego_end_s = reference_line_info->AdcSlBoundary().end_s();double ego_v =std::abs(reference_line_info->vehicle_state().linear_velocity());// 遍历障碍物,跳过虚拟的和静止的for (const auto* obstacle :reference_line_info->path_decision()->obstacles().Items()) {if (obstacle->IsVirtual() || obstacle->IsStatic()) {ADEBUG << "skip one virtual or static obstacle";continue;}// 初始化SLdouble start_s = std::numeric_limits<double>::max();double end_s = -std::numeric_limits<double>::max();double start_l = std::numeric_limits<double>::max();double end_l = -std::numeric_limits<double>::max();// 获取动态障碍物的边界点并转化为SL坐标for (const auto& p : obstacle->PerceptionPolygon().points()) {SLPoint sl_point;reference_line_info->reference_line().XYToSL(p, &sl_point);start_s = std::fmin(start_s, sl_point.s());end_s = std::fmax(end_s, sl_point.s());start_l = std::fmin(start_l, sl_point.l());end_l = std::fmax(end_l, sl_point.l());}// 以障碍物在S方向上的起始点与终点之和的二分之一作为障碍物中心点si,获取si点的道路宽度// 若障碍物在车道线之外,则不考虑if (reference_line_info->IsChangeLanePath()) {double left_width(0), right_width(0);reference_line_info->mutable_reference_line()->GetLaneWidth((start_s + end_s) * 0.5, &left_width, &right_width);if (end_l < -right_width || start_l > left_width) {continue;}}// Raw estimation on whether same direction with ADC or not based on// prediction trajectory// 根据预测轨迹粗略判断障碍物的方向是否和自车相同bool same_direction = true;if (obstacle->HasTrajectory()) {double obstacle_moving_direction =obstacle->Trajectory().trajectory_point(0).path_point().theta();const auto& vehicle_state = reference_line_info->vehicle_state();// 获取车辆航向角double vehicle_moving_direction = vehicle_state.heading();if (vehicle_state.gear() == canbus::Chassis::GEAR_REVERSE) {vehicle_moving_direction =common::math::NormalizeAngle(vehicle_moving_direction + M_PI);}double heading_difference = std::abs(common::math::NormalizeAngle(obstacle_moving_direction - vehicle_moving_direction));same_direction = heading_difference < (M_PI / 2.0);}// TODO(All) move to confsstatic constexpr double kSafeTimeOnSameDirection = 3.0;static constexpr double kSafeTimeOnOppositeDirection = 5.0;static constexpr double kForwardMinSafeDistanceOnSameDirection = 10.0;static constexpr double kBackwardMinSafeDistanceOnSameDirection = 10.0;static constexpr double kForwardMinSafeDistanceOnOppositeDirection = 50.0;static constexpr double kBackwardMinSafeDistanceOnOppositeDirection = 1.0;static constexpr double kDistanceBuffer = 0.5;double kForwardSafeDistance = 0.0;double kBackwardSafeDistance = 0.0;// 根据方向、自车与运动障碍物之间速度关系设置安全距离if (same_direction) {kForwardSafeDistance =std::fmax(kForwardMinSafeDistanceOnSameDirection,(ego_v - obstacle->speed()) * kSafeTimeOnSameDirection);kBackwardSafeDistance =std::fmax(kBackwardMinSafeDistanceOnSameDirection,(obstacle->speed() - ego_v) * kSafeTimeOnSameDirection);} else {kForwardSafeDistance =std::fmax(kForwardMinSafeDistanceOnOppositeDirection,(ego_v + obstacle->speed()) * kSafeTimeOnOppositeDirection);kBackwardSafeDistance = kBackwardMinSafeDistanceOnOppositeDirection;}// 通过滞后滤波器判断障碍物是否满足安全距离if (HysteresisFilter(ego_start_s - end_s, kBackwardSafeDistance,kDistanceBuffer, obstacle->IsLaneChangeBlocking()) &&HysteresisFilter(start_s - ego_end_s, kForwardSafeDistance,kDistanceBuffer, obstacle->IsLaneChangeBlocking())) {reference_line_info->path_decision()->Find(obstacle->Id())->SetLaneChangeBlocking(true);ADEBUG << "Lane Change is blocked by obstacle" << obstacle->Id();return false;} else {reference_line_info->path_decision()->Find(obstacle->Id())->SetLaneChangeBlocking(false);}}return true;
}

HysteresisFilter

// 滞后滤波器
// 在安全距离附近的情况下,通过引入距离缓冲区来调整安全距离的大小,从而避免频繁进行车道变换。
bool LaneChangeDecider::HysteresisFilter(const double obstacle_distance,const double safe_distance,const double distance_buffer,const bool is_obstacle_blocking) {if (is_obstacle_blocking) {// obstacle_distance是否小于safe_distance + distance_buffer,如果是则返回true,否则返回false。return obstacle_distance < safe_distance + distance_buffer;} else {// obstacle_distance是否小于safe_distance - distance_buffer,如果是则返回true,否则返回false。return obstacle_distance < safe_distance - distance_buffer;}
}

参考

[1] Apollo规划模块详解(五):算法实现-lane change decider
[2] Apollo Planning决策规划代码详细解析 (6):LaneChangeDecider
[3] 百度Apollo5.0规划模块代码学习(四)换道决策分析
[4] Apollo planning lane_change_decider解析

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TCP定制协议,序列化和反序列化

目录 前言 1.理解协议 2.网络版本计算器 2.1设计思路 2.2接口设计 2.3代码实现&#xff1a; 2.4编译测试 总结 前言 在之前的文章中&#xff0c;我们说TCP是面向字节流的&#xff0c;但是可能对于面向字节流这个概念&#xff0c;其实并不理解的&#xff0c;今天我们要介…

nginx反向代理后实现nginx和apache两种web服务器能够记录客户端的真实IP地址

一.构建环境 二.配置反向代理 1.基于源码安装的nginx环境下修改nginx.conf&#xff08;设备1&#xff09; 2.通过windows powershell进行修改hosts文件并测试 3.设备2和设备3上查看日志&#xff0c;可以看到访问来源都是代理服务器&#xff08;2.190&#xff09;而不是真实…

WebSocket服务端数据推送及心跳机制(Spring Boot + VUE)

一、WebSocket简介 HTML5规范在传统的web交互基础上为我们带来了众多的新特性&#xff0c;随着web技术被广泛用于web APP的开发&#xff0c;这些新特性得以推广和使用&#xff0c;而websocket作为一种新的web通信技术具有巨大意义。WebSocket是HTML5新增的协议&#xff0c;它的…

Python Web开发 Django 简介

今天来为大家介绍 Python 另一个 Web 开发框架 Django&#xff0c;它是一个基于 Python 定制的开源 Web 应用框架&#xff0c;最早源于一个在线新闻 Web 网站&#xff0c;后于2005年开源。Django 的功能大而全&#xff0c;它提供的一站式解决的思路&#xff0c;能让开发者不用在…

支持https访问

文章目录 1. 打开自己的云服务器的 80 和 443 端口2. 安装 nginx3. 安装 snapd4. 安装 certbot5. 生成证书6. 拷贝生成的证书到项目工作目录7. 修改 main.go 程序如下8. 编译程序9. 启动程序10. 使用 https 和端口 8081 访问页面成功11. 下面修改程序&#xff0c;支持 https 和…

C++中function,bind,lambda

c11之前&#xff0c;STL中提供了bind1st以及bind2nd绑定器 首先来看一下他们如何使用&#xff1a; 如果我们要对vector中的元素排序&#xff0c;首先会想到sort&#xff0c;比如&#xff1a; void output(const vector<int> &vec) {for (auto v : vec) {cout <&l…

js 中的原型

JavaScript规定&#xff0c;每一个构造函数都有一个prototype属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象。这个对象可以挂载函数&#xff0c;对象实例化不会多次创建原型上函数&#xff0c;节约内存。我们可以把那些不变的方法&#xff0c;直接定义在p…

【Linux】传输层协议:UDP和TCP

争做西格玛男人 文章目录 一、UDP协议1.端口号2.理解UDP报头3.UDP的特点&#xff08;面向数据报&#xff0c;全双工&#xff09; 二、TCP协议1.理解TCP报头某些TCP的策略1.1 TCP报头字段&#xff08;TCP的黏包问题&#xff09;1.2 网络协议栈和linux系统的联系&#xff08;以p…

聚观早报|京东称在技术投入没有止境;木蚁机器人完成B2轮融资

【聚观365】8月18日消息 京东零售CEO表示在技术上投入没有止境 木蚁机器人完成B2轮超亿元融资 耐能推出AI芯片KL730 三星电子泰勒晶圆厂首家客户是AI半导体厂商 韩国新能源汽车7月出口额同比大增36% 京东零售CEO表示在技术上投入没有止境 近日&#xff0c;京东零售CEO辛利…

0基础学习VR全景平台篇 第88篇:智慧眼-成员管理

一、功能说明 成员管理&#xff0c;是指管理智慧眼项目的成员&#xff0c;拥有相关权限的人可以进行添加成员、分配成员角色、设置成员分类、修改成员以及删除成员五项操作。但是仅限于管理自己的下级成员&#xff0c;上级成员无权管理。 二、前台操作页面 登录智慧眼后台操…

Maven之Servlet 版本问题

maven-archetype-webapp 骨架的 Servlet 版本问题 通过 maven-archetype-webapp 骨架去创建 java web 项目时&#xff0c;自动生成的 web.xml 配置文件所使用的 Servlet 的版本比较低&#xff08;2.3&#xff09;&#xff0c;而在低版本的 Servlet 中 EL 表达式默认是关闭的。…

iPhone卫星通信SOS功能如何在灾难中拯救生命

iPhone上的卫星紧急求救信号功能在从毛伊岛野火中拯救一家人方面发挥了至关重要的作用。这是越来越多的事件的一部分&#xff0c;在这些事件中&#xff0c;iPhone正在帮助人们摆脱危及生命的情况。 卫星提供商国际通信卫星组织负责移动的高级副总裁Mark Rasmussen在接受Lifewir…

docker compose部署zookeeper

单机部署 新建docker-compose.yaml version: 3 services:zookeeper:image: zookeeper:3.5.7container_name: base-zookeeperhostname: zookeeperprivileged: truerestart: alwaysports:- 2181:2181environment:TZ: "Asia/Shanghai"volumes:- ./volumes/zookeeper/d…

Python web实战之Django的国际化和本地化详解

关键词&#xff1a;Django、Python、Web开发、国际化&#xff08;i18n&#xff09;、本地化&#xff08;l10n&#xff09; 今天我要和大家分享一下 Python Web 开发中的一个重要话题——Django 的国际化和本地化。 1. 国际化和本地化 你有没有想过如何让你的网站在全球范围内…

fastadmin 下拉多级分类

要实现下图效果 一、先创建数据表 二、在目标的controll中引入use fast\Tree; public function _initialize() {parent::_initialize();$this->model new \app\admin\model\zxdc\Categorys;$tree Tree::instance();$tree->init(collection($this->model->order(…

框架分析(1)-IT人必须会

框架分析&#xff08;1&#xff09;-IT人必须会 专栏介绍当今主流框架前端框架后端框架移动应用框架数据库框架测试框架 Angular关键特点和功能&#xff1a;组件化架构双向数据绑定依赖注入路由功能强大的模板语法测试友好 优缺点分析优点缺点 总结 专栏介绍 link 主要对目前市…

NineData x SelectDB 完成产品兼容互认证

近日&#xff0c;新一代实时数据仓库厂商 SelectDB 与云原生智能数据管理平台 NineData 完成产品兼容互认证。经过严格的联合测试&#xff0c;双方软件完全相互兼容、功能完善、整体运行稳定且性能表现优异。基于本次的合作&#xff0c;双方将进一步为数据管理与大数据分析业务…

el-table 多个表格切换多选框显示bug

今天写了个功能&#xff0c;点击左侧的树做判断&#xff0c;一级树节点显示系统页面&#xff0c;二级树节点显示数据库页面&#xff0c;三级树节点显示表页面。 数据库页面和表页面分别有2个el-table ,上面的没有多选框&#xff0c;下面的有多选框 现在出现bug&#xff0c;在…

小型双轮差速底盘实现悬崖巡检功能

1. 功能说明 本文示例将实现R023样机小型双轮差速底盘悬崖巡检的功能。在小型双轮差速底盘上安装一个检测装置&#xff0c;它可以由1个 近红外传感器 和1个 灰度传感器 组成。近红外传感器可以识别桌面&#xff0c;灰度传感器可以识别“悬崖”&#xff0c;让机器人沿着“悬崖”…

【MySQL】事务

事务&#xff0c;我们一直没有提到过它&#xff0c;它并不影响我们书写正确的sql语句&#xff0c;但并不意味着事务就不重要 目录 一、事务的概念 二、为什么会出现事务 三、事务的版本支持 四、事务的提交方式 五、事务的常见操作方式 5.1 准备 5.2 正常演示 5.2.1 开…