维护AzerothCore服务端的朋友应该都有过这种经历:开发到后期,各种数据修复、批量任务、跨库同步的需求接踵而来,每天不是在写SQL,就是在写连接数据库的Python脚本。我自己的痛点是,pymysql裸用起来倒是不难,但每个脚本都要处理连接建立、游标管理、事务提交、异常回滚,一百个脚本就有一百种写法,维护成本高得吓人。直到我接触到acore-db-app这个包,它把AzerothCore数据库操作的脏活累活全封装好了,语法简洁,参数配置灵活,而且支持批量操作和SQL文件执行,这才算把这摊事理顺了。
这篇博文想写给两类人:一类是刚接触AzerothCore,想用Python做数据工具但不知道怎么落地下手的新手;另一类是已经在用pymysql裸写脚本,觉得重复代码太多、想找一个统一封装的老手。我会从包的安装配置开始,讲清楚核心API语法、参数体系、三个真实项目案例,最后再把我踩过的坑一并交代清楚。全程基于我实际维护过的环境来写,不是概念空谈。
1. 包从哪里来,解决的是怎样的实际痛点
1.1 AzerothCore中的数据库与Python脚本的缘分
AzerothCore作为开源魔兽世界服务端项目,运行起来之后,核心数据分布在三个库中:acore_auth负责账号与会话,acore_characters存放角色、装备、成就等玩家数据,acore_world承载生物、掉落、任务等世界静态数据。日常运维中,跨库操作非常常见——比如查一个玩家的账号状态要去auth库,查他角色的装备要去characters库,做一次活动补偿可能又要同时动world库里的掉落配置。
这些操作如果全部靠人工登录MySQL命令行执行,效率很低,尤其在需要批量处理几百上千条数据的时候。Python给出的解法简单直接:写脚本,用程序去连接数据库、跑查询、做更新。但脚本写多了之后就发现,大量的时间没花在业务逻辑上,而是花在环境搭建和重复代码上。
1.2 裸用pymysql的烦恼
我自己早期就是用pymysql直接写,代码长这样:
import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='root', database='acore_characters', charset='utf8mb4' ) cursor = conn.cursor() try: cursor.execute("SELECT guid, name FROM characters WHERE level = ?", (80,)) rows = cursor.fetchall() for row in rows: print(row) conn.commit() except Exception as e: conn.rollback() print(e) finally: cursor.close() conn.close()每个脚本都要写这一套连接、游标、异常、提交、关闭的模板代码,而且不同脚本里的参数散落各处,临时改库名、改密码就要全局搜索替换。更麻烦的是,一旦某个查询报错,往往要调半天才知道是连接配置问题还是SQL本身写错。
1.3 acore-db-app的设计思路
这个包的设计思路,说白了就是把连接管理和数据操作统一成一个可配置、可复用的能力层。它不是在pymysql之外另起炉灶,而是把pymysql包装得更友好,核心亮点有三个:
- 配置驱动:所有连接参数集中在一个配置文件里,脚本里不再出现host、user、password之类的散落常量。
- 批量优先:内置了批量插入、批量更新的便捷方法,而不是让你在for循环里反复
execute。 - 安全兜底:提供事务上下文管理器,出错了自动回滚;还提供了
dry_run预览模式,先看清楚SQL会怎么执行再落库。
简单说,它解决的不只是"少写几行代码"的问题,而是让整个数据库操作工程变得整洁有序。理解了这一点,后面看语法和参数就不会觉得是孤立的知识点。
2. 环境准备与安装:从零到可用的完整过程
2.1 版本与依赖要求
这个包基于Python 3.8以上版本开发,如果你还在用Python 2.x或者很老的3.6,建议先升级。AzerothCore服务端本身的数据库支持MySQL 5.7和8.0,这个包在两个版本下都测试过。安装它会自动拉取两个核心依赖:
pymysql:纯Python写的MySQL客户端驱动PyYAML:用来解析YAML格式的配置文件
用pip安装一行命令就行:
pip install acore-db-app国内网络环境下,如果直连官方PyPI源比较慢,可以换成国内镜像源,比如清华大学、阿里云、豆瓣的源。以清华源为例:
pip install acore-db-app -i https://pypi.tuna.tsinghua.edu.cn/simple换镜像源这一步属于常规操作,在项目文档里也经常出现,目的就是提升安装速度和稳定性。
2.2 验证安装
安装完成后,打开Python解释器或者写个一行脚本,先确认包能正常导入:
import acore_db_app print(acore_db_app.__version__)如果输出了版本号,说明基本环境没问题。接着要做的是准备配置文件,我习惯在项目根目录建一个config.yaml:
acore: auth: host: 127.0.0.1 port: 3306 user: root password: root db_name: acore_auth charset: utf8mb4 characters: host: 127.0.0.1 port: 3306 user: root password: root db_name: acore_characters charset: utf8mb4 world: host: 127.0.0.1 port: 3306 user: root password: root db_name: acore_world charset: utf8mb4这几个库之间的界限很强,后续的脚本大部分时间就在这三个连接之间切换。配置文件写好后,先跑一个最简单的查询验证整条链路:
from acore_db_app import DBManager manager = DBManager(config_path="config.yaml") result = manager.execute_select("characters", "SELECT COUNT(*) AS total FROM characters") print(result.scalar())能输出角色总数,说明连接、解析、执行全通了。这一步看着简单,但它验证的是整条基础设施,后面所有复杂操作都建立在这个基础上。
3. 核心语法详解:从连接管理到复杂查询
3.1 连接管理器:所有操作的入口
acore-db-app的核心对象是DBManager。它的作用是读取配置文件,按需建立并缓存数据库连接。你不需要手动管理连接的打开和关闭,用的时候直接调用方法,它会自动获取连接,执行完再归还到连接池。
from acore_db_app import DBManager manager = DBManager(config_path="config.yaml")这里有个设计细节值得说:DBManager初始化时并不会立即创建所有连接,而是懒加载。也就是说,你第一次访问auth库时才建立到auth库的连接,第一次访问world库时才建立到world库的连接。这个设计避免了启动时不必要的资源占用,尤其适合后续要跑很多不同脚本的场景。
连接池参数可以在配置文件里通过pool_size指定,默认是5。对于单机维护AzerothCore的环境,这个值完全够用;如果你在跑并发任务,可以适当调大。
3.2 execute_select:查询接口与结果对象
查询接口是使用频率最高的方法。以查玩家角色为例:
rows = manager.execute_select( "characters", "SELECT guid, name, level, money FROM characters WHERE level >= %s ORDER BY level DESC LIMIT 10", params=(80,) ) for row in rows: print(row.guid, row.name, row.level, row.money)语法上有几个特点值得注意。
第一,SQL里的占位符用的是%s,参数由params元组传入。这是参数化查询的标准做法,可以有效防止SQL注入。新手最容易犯的错误是把参数直接拼进SQL字符串,比如:
# 错误示范 manager.execute_select("characters", f"SELECT * FROM characters WHERE name = '{name}'")这种做法在数据量小的时候看不出问题,但一旦有用户输入进去,风险就很大。acore-db-app对参数化查询支持得不错,任何动态值都应该走params。
第二,返回的结果行是类对象,支持通过属性名访问字段,比如row.guid。这对记不清列名顺序的情况很友好,也比row[0]这样的下标访问可读性好得多。
第三,如果只需要单个值,例如统计总数,可以直接用scalar():
total = manager.execute_select("characters", "SELECT COUNT(*) AS total FROM characters").scalar()3.3 写入操作:execute_insert、execute_update与事务
写入操作分为单条和批量两种,接口设计的思路也很清晰。
单条插入:
new_guid = manager.execute_insert( "characters", "INSERT INTO guild (name, leader_guid) VALUES (%s, %s)", params=("Titans", 1234) ) print(f"新记录ID: {new_guid}")execute_insert会自动完成插入,并返回自增主键ID,方便后续关联操作。
批量插入,这才是这个包真正省时间的地方。AzerothCore里经常需要往item_instance表里批量写入物品数据,几百条数据如果循环逐条插入,建连接、提交事务的开销会拖慢整体速度。acore-db-app的批量插入接口长这样:
items = [ (1, 1001, 5), # guid, item_entry, count (1, 1002, 3), (2, 1001, 1), ] manager.execute_bulk_insert( "characters", "INSERT INTO item_instance (guid, item_entry, count) VALUES (%s, %s, %s)", items=items )批量接口内部会拼接成一条多值INSERT语句执行,效果和手动写VALUES (...), (...), (...)一样,但代码简洁很多,不会因为格式错误导致SQL拼错。
更新和删除走的是execute_update:
updated_count = manager.execute_update( "characters", "UPDATE characters SET level = %s WHERE guid = %s", params=(80, 5678) ) print(f"更新行数: {updated_count}")关于事务,默认情况下每条单独的写操作是自动提交的。如果你有几个更新需要作为一个整体来执行,任何一个失败都要回滚,可以使用transaction上下文管理器:
with manager.transaction("characters"): manager.execute_update("characters", "UPDATE characters SET money = money + 1000 WHERE guid = %s", (1001,)) manager.execute_update("characters", "UPDATE characters SET level = level + 1 WHERE guid = %s", (1001,))一旦中间某一步抛异常,事务块内的所有操作都会自动回滚,不需要手动写rollback()。这个机制在批量补偿物品、调整等级之类的场景里特别有用。
3.4 SQL文件执行:解决刷库与导数据难题
AzerothCore项目里经常需要导入现成的SQL文件——比如官方发布的修复补丁、掉落修正等等。传统做法是把SQL文件复制到MySQL容器里,再通过source命令执行,或者用本地的MySQL客户端重定向文件。这两种方式都依赖本机的数据库客户端工具,而且如果SQL文件编码不一致,导入过程中还容易报错。
acore-db-app提供了一个execute_sql_file方法,直接在Python里执行SQL文件:
manager.execute_sql_file("world", "./sql/world_drop_fix.sql")这个方法的实现细节我没深究,但实际使用下来,它对单行和多行SQL语句、注释行、DELIMITER自定义结束符都有处理,基本能应付AzerothCore社区发布的补丁格式。
有了这个能力,整个数据工作流就能完全跑在Python里了——下载SQL文件、执行、打印结果,一条龙完成。配合计划任务,还能做成自动拉取补丁并应用的更新脚本,这个我在后面的案例里会详细说。
3.5 查询辅助:当SQL开始复杂的时候
日常使用中,光靠直接用SQL字符串还不够,很多时候需要拼接动态条件。比如做一个角色查询工具,用户可能按角色名查,也可能按公会查,还可能按等级区间查。acore-db-app提供了一个QueryBuilder来做条件拼接:
from acore_db_app import DBManager from acore_db_app.query import QueryBuilder builder = QueryBuilder("characters", "SELECT guid, name, level FROM characters WHERE 1=1") if level_min: builder.add_condition("level >= %s", level_min) if name: builder.add_condition("name LIKE %s", f"%{name}%") builder.add_condition("level BETWEEN %s AND %s", (1, 80)) rows = manager.execute_raw(builder.sql(), builder.params())QueryBuilder存在的意义不是让你远离SQL,而是让动态条件的拼装过程更有条理。你仍然要自己写清晰的SQL片段,只是不用再担心多个条件之间AND和OR的优先级搞混,也不会出现漏掉空格导致SQL语法错误的尴尬。
3.6 常用方法速查
| 方法 | 作用 | 适用场景 |
|---|---|---|
execute_select | 执行查询,返回结果集 | 各类SELECT |
execute_insert | 单条插入,返回自增ID | 写入单条记录 |
execute_bulk_insert | 批量插入,自动拼接多值SQL | 大批量写入 |
execute_update | 更新或删除,返回影响行数 | UPDATE / DELETE |
execute_sql_file | 执行SQL文件 | 导入补丁、初始化数据库 |
transaction | 上下文管理器,自动提交/回滚 | 多步骤一致性操作 |
execute_raw | 直接执行SQL,适合配合QueryBuilder | 动态SQL |
这些方法基本覆盖了日常运维的所有数据操作场景。理解了它们,脚本开发就是从业务需求到方法调用的直接映射。
4. 参数配置深度解析:一份配置管好所有连接
4.1 配置文件只是起点
前面说过,配置文件是acore-db-app的配置中枢。但如果你以为只有host、port、password这几个参数,那就低估了它。实际中我使用的参数配置,远比初始示例丰富:
acore: database: default_charset: utf8mb4 pool_size: 8 connect_timeout: 5 autocommit: true auth: host: 127.0.0.1 port: 3306 user: root password: root db_name: acore_auth charset: utf8mb4 characters: host: 127.0.0.1 port: 3306 user: root password: root db_name: acore_characters charset: utf8mb4 autocommit: false world: host: 127.0.0.1 port: 3306 user: root password: root db_name: acore_world charset: utf8mb4 pool_size: 104.2 关键参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
host | 127.0.0.1 | 数据库地址,跨服务器部署时改为内网IP |
port | 3306 | 端口,本地部署一般不变 |
user/password | root / root | 数据库账号密码,建议权限最小化 |
db_name | 无 | 库名,决定连接的是哪个库 |
charset | utf8mb4 | 字符集,选错会出现中文乱码 |
pool_size | 5 | 连接池大小,并发高时调大 |
connect_timeout | 5 | 连接超时,单位秒 |
autocommit | true | 是否自动提交写操作 |
default_charset | utf8mb4 | 全局默认字符集 |
autocommit是我实际使用中比较在意的一个参数。它控制写操作是否自动提交,设置为false时可以配合事务系统做更灵活的控制——在需要手动管理提交时很有用。我在做数据迁移任务时会把目标库的autocommit关掉,全部导入完再统一提交,这样一旦中途发现数据有问题,可以直接放弃本次迁移,不让脏数据进入正式库。
4.3 CLI命令行参数:不写脚本直接干活
除了在Python里调用API,acore-db-app还提供了命令行工具,适合快速执行一条SQL或运行一个SQL文件,不需要额外写Python脚本。
acore-db --config config.yaml --db characters \ --sql "UPDATE characters SET level = 80 WHERE guid = 5678"或者执行整个SQL脚本:
acore-db --config config.yaml --db world \ --sql-file ./sql/world_drop_fix.sql --dry-run--dry-run参数是我强烈建议先跑一遍的,它只打印SQL语句的实际效果预览,不真正执行写操作。上线前跑一次dry-run,能避免很多手滑事故。
4.4 参数优先级
acore-db-app内部处理配置的顺序是:命令行参数 > 配置文件 > 环境变量 > 默认值。
这意味着你可以在不修改配置文件的情况下,用命令行临时覆盖某个参数。比如测试一个不同密码的库连接:
acore-db --config config.yaml --db characters --password test123 \ --sql "SELECT COUNT(*) FROM characters"这种层级关系让脚本部署灵活了不少。生产环境用默认配置文件,测试环境用命令行参数覆盖,两个环境之间不需要维护两份配置。
4.5 多环境配置的实践经验
另外,在维护多个项目环境时,我习惯于把配置文件的路径作为外部参数传给脚本,而不是写死在代码里:
import os from acore_db_app import DBManager config_path = os.getenv("ACORE_DB_CONFIG", "config.yaml") manager = DBManager(config_path=config_path)这样一套代码可以通过环境变量切换开发、测试、生产不同的配置,不会出现"开发环境连了生产库"这类低级事故。
5. 实际应用案例:从数据修复到自动化运维
5.1 案例一:玩家角色数据批量修复
需求场景:某次活动补偿后,发现一批角色金币没发放到位,需要给指定名单上的玩家补发金币,并且给所有在线玩家追加一个活动称号。
传统做法:手动拼接SQL,一个个执行,或者写个一次性脚本。但一次性脚本写起来啰嗦,以后还得反复写。
使用acore-db-app的做法:
from acore_db_app import DBManager manager = DBManager(config_path="config.yaml") # 根据账号查询角色,过滤出需要补偿的玩家 rows = manager.execute_select( "characters", """ SELECT c.guid, c.name, c.money, a.username FROM characters c JOIN acore_auth.account a ON c.account = a.id WHERE a.username IN (%s, %s, %s) """, params=("player1", "player2", "player3") ) # 批量补发金币 for row in rows: manager.execute_update( "characters", "UPDATE characters SET money = money + 100000 WHERE guid = %s", params=(row.guid,) ) print(f"已补发 {row.name}: {row.money} -> {row.money + 100000}")注意这里跨了两个库:字符在acore_characters,账号在acore_auth。因为MySQL不支持跨库JOIN(除非完全限定表名),我在SQL里用了acore_auth.account这种写法,配合连接管理完全可行。
效果:脚本从准备到运行完成不超过5分钟,所有补偿记录通过日志输出,方便回溯。
5.2 案例二:批量导入活动物品数据
需求场景:开新活动时,需要往item_instance表写入大量物品数据,为几千个角色发放活动礼包。
传统做法:用Navicat导入Excel,或者手动拼INSERT语句,数据量大时非常痛苦。
使用acore-db-app的做法:
from acore_db_app import DBManager manager = DBManager(config_path="config.yaml") # 从CSV读取角色和物品对应关系 import csv items_to_insert = [] with open("activity_items.csv", "r", encoding="utf-8") as f: reader = csv.reader(f) next(reader) for row in reader: guid, item_entry, count = row items_to_insert.append((int(guid), int(item_entry), int(count))) # 分批批量插入,每批5000条 BATCH_SIZE = 5000 for i in range(0, len(items_to_insert), BATCH_SIZE): batch = items_to_insert[i:i + BATCH_SIZE] manager.execute_bulk_insert( "characters", "INSERT INTO item_instance (guid, item_entry, count) VALUES (%s, %s, %s)", items=batch ) print(f"已导入 {i + len(batch)} / {len(items_to_insert)} 条")细节说明:为什么要分批?一是避免单次拼接SQL过长超过MySQL的max_allowed_packet限制;二是分批可以及时看到进度,某批出错时日志能定位到具体范围。这个经验我在处理几万条数据时反复验证过,非常实用。
5.3 案例三:SQL补丁自动应用与版本追踪
需求场景:AzerothCore社区会持续发布数据库补丁,手动逐个导入容易乱。希望做一个工具,能检测目录下的新SQL文件,自动按顺序执行,并防止重复执行。
使用acore-db-app的做法:
import glob import os import hashlib from acore_db_app import DBManager manager = DBManager(config_path="config.yaml") # 读取已执行的SQL文件列表 applied_rows = manager.execute_select( "world", "SELECT file_hash FROM applied_sql_log" ) applied_hashes = {row.file_hash for row in applied_rows} sql_files = sorted(glob.glob("./sql/*.sql")) new_scripts = [] for sql_file in sql_files: with open(sql_file, "rb") as f: file_hash = hashlib.md5(f.read()).hexdigest() if file_hash not in applied_hashes: new_scripts.append((sql_file, file_hash)) for sql_file, file_hash in new_scripts: print(f"正在执行: {sql_file}") with manager.transaction("world"): manager.execute_sql_file("world", sql_file) manager.execute_insert( "world", "INSERT INTO applied_sql_log (file_hash, file_name, applied_at) VALUES (%s, %s, NOW())", params=(file_hash, os.path.basename(sql_file)) )核心思想:用MD5哈希值记录已应用的文件,同一个文件即使重跑一遍也会被跳过,天然幂等。配合计划任务,可以实现补丁的自动拉取、自动校验、自动应用、自动记录,比手工导入省心太多。这个模式我推荐给所有维护量较大的读者使用。
5.4 案例四:定时巡检与异常告警
需求场景:定期检查服务端数据库的一些关键指标——比如角色表是否异常膨胀、某张表是否损坏、账号表有没有异常登录记录等。发现问题后用企业微信机器人或邮件发通知。
from acore_db_app import DBManager manager = DBManager(config_path="config.yaml") checks = { "角色表大小": "SELECT COUNT(*) AS total FROM characters", "异常账号": "SELECT COUNT(*) AS total FROM account WHERE login_count > 1000", "重复角色": "SELECT name, COUNT(*) AS c FROM characters GROUP BY name HAVING c > 1", } alerts = [] for name, sql in checks.items(): try: total = manager.execute_select("characters", sql).scalar() if total > 0: alerts.append(f"{name}: {total}") except Exception as e: alerts.append(f"{name}: 查询失败 {e}") if alerts: send_notification("数据库巡检异常", "\n".join(alerts))这个脚本挂在服务器定时任务里,每天跑一次,能第一时间发现数据异常,不用等玩家反馈问题才发现出事了。
6. 踩坑记录与调试技巧
6.1 字符集不对导致的中文乱码
踩坑过程:某次导入SQL文件后,发现游戏里任务名字全部变成问号。排查链路:先怀疑是游戏客户端缓存问题,清理后依旧;然后怀疑SQL文件本身编码问题,用file命令查看文件编码,发现UTF-8没问题;最后无意中看到链接字符集是latin1,才确定是连接数据库时的字符集设置不对。
根因:配置文件里没有显式指定charset参数,包用了默认的latin1,和库里的utf8mb4字符集不匹配。解决办法很简单,在配置文件中把charset统一改为utf8mb4。这个坑在我第一次使用这个包时踩过,从那之后我配置连接参数时永远会显式指定字符集。
6.2 大批量更新操作时的性能陷阱
踩坑过程:之前做一次全服金币补偿,用for循环逐条执行UPDATE,结果跑了十几分钟还没跑完,而且对在线玩家产生了明显的卡顿。
根因:逐条UPDATE会产生大量小事务,每次都要进行磁盘同步,性能自然低下。而且AzerothCore运行期间,对characters表的高频写会让主线程等待锁,影响服务端响应。
解决方案:改用批量更新接口,把多条UPDATE合并成一条多值SQL,或者使用CASE WHEN结构批量更新:
# 一次更新多个玩家不同金额 cases = [ (1001, 5000), (1002, 3000), (1003, 8000), ] values = ",".join(["WHEN guid = %s THEN money + %s"] * len(cases)) params = [] for guid, amount in cases: params.extend([guid, amount]) params.append([guid for guid, _ in cases]) manager.execute_update( "characters", f"UPDATE characters SET money = CASE {values} ELSE money END WHERE guid IN (%s)" % ",".join(["%s"] * len(cases)), params=params )改进后执行时间从十几分钟降到了几秒,服务端也完全没有卡顿。
6.3 关于SQL注入的一点安全感
踩坑过程:开发查询工具时,我把用户输入的角色名直接拼进SQL,结果某次测试输入了一个带单引号的名字,SQL直接报错。这时才意识到注入风险不仅存在于外部攻击,正常的业务数据也可能包含特殊字符。
经验:无论数据是否来自外部用户,只要是不确定的动态值,一律放到params参数里,不要用字符串拼接。acore-db-app的占位符模式写起来简单,完全没理由去走拼接这条路。
6.4 dry-run模式的妙用
踩坑过程:某次批量更新前,我想当然地认为SQL没问题,直接跑了,结果把所有角色的等级都改成了80。当时从没听说过有dry-run这种玩法,直到用了acore-db-app才发现CLI里自带。
经验:凡是涉及写操作的脚本,上线前先用--dry-run跑一遍。这个习惯养成了之后,几乎没再出现过把测试环境数据改坏、或者把正式环境数据误改的恐慌。
6.5 连接池溢出的处理方式
踩坑过程:写长时间运行的采集脚本时,忘记关闭连接或者连接池太小,跑了几个小时后连接数飚到几百,数据库直接拒绝新连接。
解决方案:把pool_size调大,并且把connect_timeout设置成一个合理的值。同时注意用完的DBManager对象在长时间脚本里最后调用manager.close()释放所有连接。
try: # 长时间运行的逻辑 pass finally: manager.close()这个细节在短脚本里无所谓,但放到常驻服务或计划任务里,就是会不会把数据库连接池打满的区别。
7. 实际使用中的一些额外技巧
最后分享几个我在实际项目中摸索出来的小技巧。
第一个是尽量用配置文件管理密码,而不是写在代码里。配合环境变量覆盖,既能保护敏感信息,又方便在不同环境间切换。
第二个是SQL文件执行的日志层面做完整记录。我在执行execute_sql_file时,会在脚本里把文件名、执行时间、执行结果都记录到日志文件里。后期排查问题时,能准确知道每个补丁是什么时候、由哪个脚本应用进去的。
第三个是事务块里只放必要的语句。有过一次经历,事务块里放了一个耗时的批量插入,结果几个事务并发等待锁,拖慢了整个服务端。现在的原则是,事务块越小越好,长耗时的操作尽量放到事务外执行。
第四个是善用scalar()和结果行对象。很多刚上手的朋友习惯用fetchone()、fetchall()去操作返回结果,但acore-db-app已经把这些封装成了更舒服的方式。row.field_name这种属性访问在IDE里还能有类型提示,写起来比下标访问少很多低级错误。
8. 我对这个包的整体评价
维护AzerothCore服务端两年多,我用过不少数据库操作方案,最终还是固定在了acore-db-app上。它的设计思路很适合个人项目或中小型团队:配置驱动减少出错、批量操作省时省力、事务管理保护数据安全、SQL文件执行简化部署。同时它没有过度设计,核心就是围绕AzerothCore数据库场景做好的那几个接口,没有一大堆用不上的炫技功能。
如果你正被一堆裸pymysql脚本折磨,或者正准备为AzerothCore写第一个数据工具,我建议你先从这个包入手。服务端的数据管理落到实处就是增删改查加定时任务,acore-db-app把所有琐碎细节处理好了,你可以把精力放在真正关心的业务逻辑上。我的所有脚本都是从上面这些代码模式改出来的,非常稳定,值得一试。