摘要
同一台开发机同时承载 ROS 2 开发环境和 Drogon 后端时,问题往往不在数据库服务本身,而在开发库。至少在 Ubuntu Jammy 的官方包里,安装部分 ROS 2 相关包后,系统里可能已经使用 libmysqlclient-dev;而 Drogon v1.9.9 这一版要启用 MySQL 支持,实际更依赖 libmariadb-dev 和 libmariadb-dev-compat 这一组开发包。两边单独使用都没问题,放到同一套构建环境里后,就容易卡在开发库冲突和接口不匹配上。
这类问题通常出现在这样的场景里:系统已经装有 ROS 2 相关开发环境,后端准备接 Drogon + MySQL,但 Drogon v1.9.9 在 Linux 下查找客户端库、以及使用非阻塞数据库 API 的这套逻辑,更接近 MariaDB 文档里的 *_start/*_cont 接口。只要系统当前安装的是 libmysqlclient-dev 这一侧,而不是 Drogon 预期的 MariaDB Connector/C 开发库,编译就很容易失败。
本文不讨论“最优架构”,只记录一个临时兼容方案:给 Drogon v1.9.9 打一个补丁,让它先识别当前客户端库是否提供 mysql_real_connect_start 这类符号;如果没有,就退回阻塞实现,先恢复构建和基本可用性。
一、问题背景
这个问题表面上看像是“数据库冲突”,实际更接近开发库选择与 Drogon 代码假设之间的冲突。
先限定一下讨论范围。这里说的是开发机上的数据库开发库兼容性,而不是“整个 ROS 2 默认依赖 MySQL”这样的结论。至于具体是哪几个 ROS 2 相关包会带来这套环境,不同发行版、安装方式和系统仓库组合都可能不同,因此不在文中固定列出。
这类场景通常有几个共同点:
- 当前系统已经装好了 ROS 2 开发环境,并额外安装了部分 ROS 2 相关包
- 后端项目准备用 Drogon
- 部分 ROS 2 相关包使用
libmysqlclient-dev - Drogon
v1.9.9要启用 MySQL 支持时,更接近libmariadb-dev及其兼容包libmariadb-dev-compat这套开发库路径
这里讨论的也不是 MySQL Server 和 MariaDB Server 能否在理论上共存,而是源码构建时用到的开发库,是否与 Drogon v1.9.9 的 API 假设一致。
问题首先体现在包管理和开发库接口两层:
- 至少在 Ubuntu Jammy 的官方包关系里,
libmariadb-dev与libmariadb-dev-compat都会Conflicts/Breaks/Replaces libmysqlclient-dev,因此两套开发包不能直接共装 - Drogon
v1.9.9的MysqlConnection默认走了MYSQL_OPT_NONBLOCK和mysql_real_connect_start这一类接口路径,这条路径更接近 MariaDB Connector/C,而不是 Oracle MySQL 8 的_nonblocking接口
因此,问题往往不在 mysqld 或 mariadbd 服务是否启动,而在于当前机器到底装的是哪一套开发库。 如果当前头文件和库里没有 Drogon 这条代码路径需要的符号,构建会直接失败。即便通过其他方式绕过编译阶段,后续连接和查询流程也可能不稳定。
二、冲突的根因
核心问题不在于库的命名,而在于当前安装的开发库到底提供了哪些能力。
Drogon v1.9.9 的顶层 CMakeLists.txt 在 BUILD_MYSQL 分支里直接注明了 only mariadb client library is supported。从 Ubuntu Jammy 的包说明看,libmariadb-dev 用于面向 MariaDB Connector/C 的源码,libmariadb-dev-compat 则提供“让期望 MySQL client libraries 的源码构建到 MariaDB Connector/C 上”的兼容符号链接;而这两个包都与 libmysqlclient-dev 冲突。对应的 MysqlConnection.cc 里也确实直接调用了下面这些接口:
MYSQL_OPT_NONBLOCKmysql_real_connect_startmysql_real_connect_contmysql_real_query_startmysql_store_result_startmysql_set_character_set_start
MariaDB 官方文档把非阻塞客户端接口写成 *_start/*_cont 这一组;而 MySQL 8 官方 C API 异步接口文档写的是 mysql_real_connect_nonblocking()、mysql_real_query_nonblocking()、mysql_store_result_nonblocking() 这一组。 因此,关键不在库名本身,而在于当前头文件和客户端库到底导出了哪一组 API。
如果当前环境里装的是 libmysqlclient-dev,而没有 Drogon 这条代码路径所需的 *_start/*_cont 或 MYSQL_OPT_NONBLOCK 符号,按原逻辑编译就会遇到符号或能力不匹配的问题。
这个补丁的处理思路如下:
- 先把 MySQL 客户端库查找范围补全
- 再显式检测当前头文件里有没有
mysql_real_connect_start - 如果有,就继续走 Drogon 原来的异步路径
- 如果没有,就退回阻塞连接和阻塞查询路径
它不是完整重构,而是一个以兼容性为目标的临时修正。
三、处理方案
以下步骤直接基于 Drogon v1.9.9 打补丁。
先拉代码并初始化子模块:
git clone https://github.com/drogonframework/drogon -b v1.9.9
cd drogon
git submodule update --init
把下面的内容保存成 drogon-mysql-fix.patch,然后应用:
git apply drogon-mysql-fix.patch
补丁打完以后,按正常方式编译安装即可:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
sudo cmake --install build
如果之前在外层工程里显式关掉了 BUILD_MYSQL,记得补上 -DBUILD_MYSQL=ON。 Drogon v1.9.9 源码默认是开的,但前提是 BUILD_ORM 也处于开启状态。
如果安装路径、编译选项或者工具链有特殊要求,可按实际环境调整。 重点不在构建命令本身,而在于让 Drogon 先正确识别当前客户端库能力。
四、补丁改了什么
这份补丁主要包含四类修改。
(一)补全 FindMySQL.cmake 的查找逻辑
原始版本在 Linux 下查找库文件时,更偏向以下名称:
mysqlclient_rmariadbclientmariadb
补丁把 mysqlclient 也纳入查找范围,同时补充了常见库目录,例如:
/usr/lib/usr/lib64/usr/lib/x86_64-linux-gnu/usr/local/lib
这样做的目的很明确:避免 CMake 因为路径和库名范围过窄,漏掉系统里已经存在的 MySQL 客户端库。
(二)在 CMake 阶段检测是否存在非阻塞 API
补丁里新增了这样一段检查:
include(CheckSymbolExists)
check_symbol_exists(mysql_real_connect_start "mysql.h" HAS_MYSQL_NONBLOCK_API)
if (HAS_MYSQL_NONBLOCK_API)
add_definitions(-DHAS_MYSQL_NONBLOCK_API)
endif ()
这一步很关键。 它不是依赖库名推断,而是直接检测头文件里是否存在 mysql_real_connect_start 这个符号。
有这个符号,就说明当前可以继续使用 Drogon 原本依赖的非阻塞实现。 没有这个符号,就不再强行编译异步路径,而是切到兼容的阻塞分支。
(三)给 MysqlConnection 增加阻塞 fallback
这是补丁中改动最大的一部分。
原始实现默认走非阻塞流程,核心逻辑依赖:
MYSQL_OPT_NONBLOCKmysql_real_connect_start / contmysql_real_query_start / contmysql_set_character_set_start / contmysql_store_result_start / cont
补丁在 HAS_MYSQL_NONBLOCK_API 不存在时,补了一套阻塞路径:
- 连接阶段直接调用
mysql_real_connect - 字符集设置直接调用
mysql_set_character_set - 查询阶段直接调用
mysql_real_query - 结果集读取直接调用
mysql_store_result
同时新增了:
startQueryBlocking()getResultBlocking(MYSQL_RES *res)
这样改完以后,Drogon 至少可以在缺少 *_start/*_cont 这组符号的前提下完成连接、查询和结果回调,避免在编译阶段直接失败。
但需要明确一点:这个 fallback 只是兼容性兜底,不是语义完全等价的替换。 因为这些阻塞调用发生在该数据库连接所属的 EventLoop 线程里,所以它会改变原来这条连接的并发与时延特性。
(四)补充了一些防御性处理
补丁里还补充了两类防御性处理:
channelPtr_为空时的判空保护exceptionCallback_调用时的异常兜底
这两处不是本次兼容性问题的主因,但在 fallback 分支存在时,防御性代码可以降低异常路径上的二次崩溃风险。
五、完整补丁
如果只想先复现这套方案,直接把下面内容保存成 drogon-mysql-fix.patch 即可:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7cf091e9..980d5628 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -414,6 +414,11 @@ if (BUILD_MYSQL)
if (DROGON_FOUND_MYSQL)
message(STATUS "Ok! We find mariadb!")
+ include(CheckSymbolExists)
+ check_symbol_exists(mysql_real_connect_start "mysql.h" HAS_MYSQL_NONBLOCK_API)
+ if (HAS_MYSQL_NONBLOCK_API)
+ add_definitions(-DHAS_MYSQL_NONBLOCK_API)
+ endif ()
include(CheckLibraryExists)
check_library_exists(${MYSQL_LIB_NAME} mysql_optionsv "" HAS_MYSQL_OPTIONSV)
if (HAS_MYSQL_OPTIONSV)
diff --git a/cmake_modules/FindMySQL.cmake b/cmake_modules/FindMySQL.cmake
index ab598152..881a1456 100644
--- a/cmake_modules/FindMySQL.cmake
+++ b/cmake_modules/FindMySQL.cmake
@@ -99,8 +99,16 @@ if(WIN32)
$ENV{SystemDrive}/MySQL/*/lib/${libsuffixDist})
else(WIN32)
find_library(MYSQL_LIBRARIES
- NAMES mysqlclient_r mariadbclient mariadb
- PATHS /usr/lib/mysql
+ NAMES mysqlclient mysqlclient_r mariadbclient mariadb
+ PATHS /usr/lib
+ /usr/lib64
+ /usr/lib/x86_64-linux-gnu
+ /usr/lib/aarch64-linux-gnu
+ /usr/lib/arm-linux-gnueabihf
+ /usr/lib/arm-linux-gnueabi
+ /usr/local/lib
+ /usr/local/lib64
+ /usr/lib/mysql
/usr/lib/mariadb
/usr/local/lib/mysql
/usr/local/lib/mariadb
diff --git a/orm_lib/src/mysql_impl/MysqlConnection.cc b/orm_lib/src/mysql_impl/MysqlConnection.cc
index fb352fec..a9d4e7dc 100644
--- a/orm_lib/src/mysql_impl/MysqlConnection.cc
+++ b/orm_lib/src/mysql_impl/MysqlConnection.cc
@@ -59,7 +59,9 @@ MysqlConnection::MysqlConnection(trantor::EventLoop *loop,
static MysqlEnv env;
static thread_local MysqlThreadEnv threadEnv;
mysql_init(mysqlPtr_.get());
+##if defined(HAS_MYSQL_NONBLOCK_API)
mysql_options(mysqlPtr_.get(), MYSQL_OPT_NONBLOCK, nullptr);
+##endif
##ifdef HAS_MYSQL_OPTIONSV
mysql_optionsv(mysqlPtr_.get(), MYSQL_OPT_RECONNECT, &reconnect_);
##endif
@@ -108,6 +110,7 @@ void MysqlConnection::init()
loop_->queueInLoop([this]() {
MYSQL *ret;
status_ = ConnectStatus::Connecting;
+##if defined(HAS_MYSQL_NONBLOCK_API)
waitStatus_ =
mysql_real_connect_start(&ret,
mysqlPtr_.get(),
@@ -135,11 +138,57 @@ void MysqlConnection::init()
channelPtr_ = std::make_unique<trantor::Channel>(loop_, fd);
channelPtr_->setEventCallback([this]() { handleEvent(); });
setChannel();
+##else
+ ret = mysql_real_connect(mysqlPtr_.get(),
+ host_.empty() ? nullptr : host_.c_str(),
+ user_.empty() ? nullptr : user_.c_str(),
+ passwd_.empty() ? nullptr : passwd_.c_str(),
+ dbname_.empty() ? nullptr : dbname_.c_str(),
+ port_.empty() ? 3306 : atol(port_.c_str()),
+ nullptr,
+ 0);
+ if (!ret)
+ {
+ LOG_ERROR << "Error(" << mysql_errno(mysqlPtr_.get()) << ") \""
+ << mysql_error(mysqlPtr_.get()) << "\"";
+ LOG_ERROR << "Failed to mysql_real_connect()";
+ handleClosed();
+ return;
+ }
+ if (characterSet_.empty())
+ {
+ status_ = ConnectStatus::Ok;
+ if (okCallback_)
+ {
+ auto thisPtr = shared_from_this();
+ okCallback_(thisPtr);
+ }
+ }
+ else
+ {
+ if (mysql_set_character_set(mysqlPtr_.get(), characterSet_.data()) !=
+ 0)
+ {
+ LOG_ERROR << "Error(" << mysql_errno(mysqlPtr_.get()) << ") \""
+ << mysql_error(mysqlPtr_.get()) << "\"";
+ LOG_ERROR << "Failed to mysql_set_character_set()";
+ handleClosed();
+ return;
+ }
+ status_ = ConnectStatus::Ok;
+ if (okCallback_)
+ {
+ auto thisPtr = shared_from_this();
+ okCallback_(thisPtr);
+ }
+ }
+##endif
});
}
void MysqlConnection::setChannel()
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
if ((waitStatus_ & MYSQL_WAIT_READ) || (waitStatus_ & MYSQL_WAIT_EXCEPT))
{
if (!channelPtr_->isReading())
@@ -161,6 +210,7 @@ void MysqlConnection::setChannel()
auto thisPtr = shared_from_this();
loop_->runAfter(timeout, [thisPtr]() { thisPtr->handleTimeout(); });
}
+##endif
}
void MysqlConnection::handleClosed()
@@ -169,8 +219,11 @@ void MysqlConnection::handleClosed()
if (status_ == ConnectStatus::Bad)
return;
status_ = ConnectStatus::Bad;
- channelPtr_->disableAll();
- channelPtr_->remove();
+ if (channelPtr_)
+ {
+ channelPtr_->disableAll();
+ channelPtr_->remove();
+ }
assert(closeCallback_);
auto thisPtr = shared_from_this();
closeCallback_(thisPtr);
@@ -183,8 +236,11 @@ void MysqlConnection::disconnect()
auto f = pro.get_future();
loop_->runInLoop([thisPtr, &pro]() {
thisPtr->status_ = ConnectStatus::Bad;
- thisPtr->channelPtr_->disableAll();
- thisPtr->channelPtr_->remove();
+ if (thisPtr->channelPtr_)
+ {
+ thisPtr->channelPtr_->disableAll();
+ thisPtr->channelPtr_->remove();
+ }
thisPtr->mysqlPtr_.reset();
pro.set_value(1);
});
@@ -193,6 +249,7 @@ void MysqlConnection::disconnect()
void MysqlConnection::handleTimeout()
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
int status = 0;
status |= MYSQL_WAIT_TIMEOUT;
MYSQL *ret;
@@ -235,10 +292,12 @@ void MysqlConnection::handleTimeout()
else if (status_ == ConnectStatus::Ok)
{
}
+##endif
}
void MysqlConnection::handleCmd(int status)
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
switch (execStatus_)
{
case ExecStatus::RealQuery:
@@ -308,10 +367,14 @@ void MysqlConnection::handleCmd(int status)
default:
return;
}
+##else
+ (void)status;
+##endif
}
void MysqlConnection::handleEvent()
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
int status = 0;
auto revents = channelPtr_->revents();
if (revents & POLLIN)
@@ -361,10 +424,12 @@ void MysqlConnection::handleEvent()
{
continueSetCharacterSet(status);
}
+##endif
}
void MysqlConnection::continueSetCharacterSet(int status)
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
int err;
waitStatus_ = mysql_set_character_set_cont(&err, mysqlPtr_.get(), status);
if (waitStatus_ == 0)
@@ -385,10 +450,14 @@ void MysqlConnection::continueSetCharacterSet(int status)
}
}
setChannel();
+##else
+ (void)status;
+##endif
}
void MysqlConnection::startSetCharacterSet()
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
int err;
waitStatus_ = mysql_set_character_set_start(&err,
mysqlPtr_.get(),
@@ -415,6 +484,22 @@ void MysqlConnection::startSetCharacterSet()
status_ = ConnectStatus::SettingCharacterSet;
}
setChannel();
+##else
+ if (mysql_set_character_set(mysqlPtr_.get(), characterSet_.data()) != 0)
+ {
+ LOG_ERROR << "Error(" << mysql_errno(mysqlPtr_.get()) << ") \""
+ << mysql_error(mysqlPtr_.get()) << "\"";
+ LOG_ERROR << "Failed to mysql_set_character_set()";
+ handleClosed();
+ return;
+ }
+ status_ = ConnectStatus::Ok;
+ if (okCallback_)
+ {
+ auto thisPtr = shared_from_this();
+ okCallback_(thisPtr);
+ }
+##endif
}
void MysqlConnection::execSqlInLoop(
@@ -523,7 +608,10 @@ void MysqlConnection::execSqlInLoop(
void MysqlConnection::outputError()
{
- channelPtr_->disableAll();
+ if (channelPtr_)
+ {
+ channelPtr_->disableAll();
+ }
auto errorNo = mysql_errno(mysqlPtr_.get());
LOG_ERROR << "Error(" << errorNo << ") [" << mysql_sqlstate(mysqlPtr_.get())
<< "] \"" << mysql_error(mysqlPtr_.get()) << "\"";
@@ -531,9 +619,23 @@ void MysqlConnection::outputError()
if (isWorking_)
{
// TODO: exception type
- auto exceptPtr = std::make_exception_ptr(
- SqlError(mysql_error(mysqlPtr_.get()), sql_));
- exceptionCallback_(exceptPtr);
+ auto exceptPtr =
+ std::make_exception_ptr(SqlError(mysql_error(mysqlPtr_.get()), sql_));
+ if (exceptionCallback_)
+ {
+ try
+ {
+ exceptionCallback_(exceptPtr);
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR << "exception callback threw: " << e.what();
+ }
+ catch (...)
+ {
+ LOG_ERROR << "exception callback threw unknown exception";
+ }
+ }
exceptionCallback_ = nullptr;
callback_ = nullptr;
@@ -551,6 +653,7 @@ void MysqlConnection::outputError()
void MysqlConnection::startQuery()
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
int err;
// int mysql_real_query_start(int *ret, MYSQL *mysql, const char *q,
// unsigned long length)
@@ -571,10 +674,14 @@ void MysqlConnection::startQuery()
}
startStoreResult(true);
}
+##else
+ startQueryBlocking();
+##endif
}
void MysqlConnection::startStoreResult(bool queueInLoop)
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
MYSQL_RES *ret;
execStatus_ = ExecStatus::StoreResult;
waitStatus_ = mysql_store_result_start(&ret, mysqlPtr_.get());
@@ -606,10 +713,14 @@ void MysqlConnection::startStoreResult(bool queueInLoop)
getResult(ret);
}
}
+##else
+ (void)queueInLoop;
+##endif
}
void MysqlConnection::getResult(MYSQL_RES *res)
{
+##if defined(HAS_MYSQL_NONBLOCK_API)
auto resultPtr = std::shared_ptr<MYSQL_RES>(res, [](MYSQL_RES *r) {
mysql_free_result(r);
});
@@ -644,4 +755,126 @@ void MysqlConnection::getResult(MYSQL_RES *res)
}
}
}
+##else
+ (void)res;
+##endif
+}
+
+void MysqlConnection::startQueryBlocking()
+{
+ execStatus_ = ExecStatus::None;
+ int err = mysql_real_query(mysqlPtr_.get(), sql_.c_str(), sql_.length());
+ if (err)
+ {
+ LOG_ERROR << "error:" << err;
+ outputError();
+ return;
+ }
+ MYSQL_RES *ret = mysql_store_result(mysqlPtr_.get());
+ if (!ret && mysql_errno(mysqlPtr_.get()))
+ {
+ outputError();
+ return;
+ }
+ getResultBlocking(ret);
+}
+
+void MysqlConnection::getResultBlocking(MYSQL_RES *res)
+{
+ auto resultPtr = std::shared_ptr<MYSQL_RES>(res, [](MYSQL_RES *r) {
+ mysql_free_result(r);
+ });
+ auto Result = makeResult(std::move(resultPtr),
+ mysql_affected_rows(mysqlPtr_.get()),
+ mysql_insert_id(mysqlPtr_.get()));
+ if (!isWorking_)
+ {
+ return;
+ }
+ try
+ {
+ callback_(Result);
+ }
+ catch (...)
+ {
+ auto exceptPtr = std::current_exception();
+ if (exceptionCallback_)
+ {
+ try
+ {
+ exceptionCallback_(exceptPtr);
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR << "exception callback threw: " << e.what();
+ }
+ catch (...)
+ {
+ LOG_ERROR << "exception callback threw unknown exception";
+ }
+ }
+ callback_ = nullptr;
+ exceptionCallback_ = nullptr;
+ isWorking_ = false;
+ idleCb_();
+ return;
+ }
+ while (mysql_more_results(mysqlPtr_.get()))
+ {
+ int err = mysql_next_result(mysqlPtr_.get());
+ if (err)
+ {
+ LOG_ERROR << "error:" << err;
+ outputError();
+ return;
+ }
+ MYSQL_RES *next = mysql_store_result(mysqlPtr_.get());
+ if (!next && mysql_errno(mysqlPtr_.get()))
+ {
+ outputError();
+ return;
+ }
+ auto nextPtr = std::shared_ptr<MYSQL_RES>(next, [](MYSQL_RES *r) {
+ mysql_free_result(r);
+ });
+ auto nextResult = makeResult(std::move(nextPtr),
+ mysql_affected_rows(mysqlPtr_.get()),
+ mysql_insert_id(mysqlPtr_.get()));
+ if (!isWorking_)
+ {
+ return;
+ }
+ try
+ {
+ callback_(nextResult);
+ }
+ catch (...)
+ {
+ auto exceptPtr = std::current_exception();
+ if (exceptionCallback_)
+ {
+ try
+ {
+ exceptionCallback_(exceptPtr);
+ }
+ catch (const std::exception &e)
+ {
+ LOG_ERROR << "exception callback threw: " << e.what();
+ }
+ catch (...)
+ {
+ LOG_ERROR << "exception callback threw unknown exception";
+ }
+ }
+ callback_ = nullptr;
+ exceptionCallback_ = nullptr;
+ isWorking_ = false;
+ idleCb_();
+ return;
+ }
+ }
+ callback_ = nullptr;
+ exceptionCallback_ = nullptr;
+ isWorking_ = false;
+ idleCb_();
}
diff --git a/orm_lib/src/mysql_impl/MysqlConnection.h b/orm_lib/src/mysql_impl/MysqlConnection.h
index 4ec0c618..5a1833bf 100644
--- a/orm_lib/src/mysql_impl/MysqlConnection.h
+++ b/orm_lib/src/mysql_impl/MysqlConnection.h
@@ -133,6 +133,8 @@ class MysqlConnection : public DbConnection,
std::function<void(const std::exception_ptr &)> &&exceptCallback);
void startSetCharacterSet();
void continueSetCharacterSet(int status);
+ void startQueryBlocking();
+ void getResultBlocking(MYSQL_RES *res);
std::unique_ptr<trantor::Channel> channelPtr_;
std::shared_ptr<MYSQL> mysqlPtr_;
std::string characterSet_;
六、这个方案的边界
这个方案可以解决“当前环境中无法编译、无法使用”的问题,但代价也很明确。
最需要提前说清楚的一点是:fallback 到阻塞实现以后,数据库操作会失去原来那套基于 *_start/*_cont 的非阻塞驱动方式。
这意味着:
- 如果服务并发不高,这个方案通常可以满足需求
- 如果查询很重,阻塞路径会阻塞该数据库连接所在的 EventLoop 线程
- 如果本来就依赖 Drogon 这条异步 MySQL 路径的时延特性,这只是临时过渡,不是长期方案
标题里使用“兼容补丁”而不是“最终修复方案”,原因就在这里。
长期看,更稳的方向通常还是这几类:
- 把 ROS 2 和后端构建环境拆开
- 用容器把依赖隔离开
- 等上游修复或自己整理成更完整的兼容提交
- 在数据库客户端选型上彻底统一
如果当前目标只是让 ROS 2 开发环境和 Drogon 后端在同一台机器上完成构建并恢复基本可用性,这个补丁可以作为临时方案。 如果后续要进入生产环境或高并发场景,仍然应该回到依赖治理和运行模型本身解决问题。
结论
ROS 2 开发环境和 Drogon 放到同一套系统里时,真正的冲突点通常不在“数据库名字”,而在于 Drogon 对客户端库能力的假设过于具体。
临时处理思路可以概括为:
- 让 CMake 先认出
mysqlclient - 再检测当前是否具备
mysql_real_connect_start这类*_start/*_cont符号 - 有就走原始异步路径
- 没有就退回阻塞实现
这不是最终形态的解决方案,但足够直接,也符合眼前最实际的目标:先恢复构建、连接和基本运行能力。
评论区