diff --git a/backend/database/fix_exit_order_id_unique.sql b/backend/database/fix_exit_order_id_unique.sql new file mode 100644 index 0000000..406484b --- /dev/null +++ b/backend/database/fix_exit_order_id_unique.sql @@ -0,0 +1,50 @@ +-- 修复 exit_order_id 唯一约束问题 +-- 问题:exit_order_id 有 UNIQUE 约束,但在状态同步时,同一个订单可能被多次更新,导致重复键错误 +-- 解决方案:移除 UNIQUE 约束,保留索引以便快速查询 + +USE `auto_trade_sys`; + +-- 1. 删除唯一约束(如果存在) +-- 注意:MySQL 中,如果字段有 UNIQUE 约束,删除索引会同时删除唯一约束 +-- 先检查是否存在唯一索引 +SET @index_exists = ( + SELECT COUNT(*) + FROM information_schema.statistics + WHERE table_schema = 'auto_trade_sys' + AND table_name = 'trades' + AND index_name = 'exit_order_id' + AND non_unique = 0 +); + +-- 如果存在唯一索引,删除它 +SET @sql = IF(@index_exists > 0, + 'ALTER TABLE `trades` DROP INDEX `exit_order_id`', + 'SELECT "exit_order_id 唯一索引不存在,跳过删除" as message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 2. 重新添加普通索引(非唯一) +-- 先检查是否已存在普通索引 +SET @normal_index_exists = ( + SELECT COUNT(*) + FROM information_schema.statistics + WHERE table_schema = 'auto_trade_sys' + AND table_name = 'trades' + AND index_name = 'idx_exit_order_id' +); + +-- 如果不存在普通索引,添加它 +SET @sql2 = IF(@normal_index_exists = 0, + 'ALTER TABLE `trades` ADD INDEX `idx_exit_order_id` (`exit_order_id`)', + 'SELECT "idx_exit_order_id 索引已存在,跳过添加" as message' +); +PREPARE stmt2 FROM @sql2; +EXECUTE stmt2; +DEALLOCATE PREPARE stmt2; + +-- 验证:检查表结构 +-- SHOW CREATE TABLE `trades`; +-- 或者 +-- SHOW INDEX FROM `trades` WHERE Column_name = 'exit_order_id'; diff --git a/backend/database/models.py b/backend/database/models.py index 1244688..00655c9 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -120,16 +120,63 @@ class Trade: pnl: 盈亏 pnl_percent: 盈亏百分比 exit_order_id: 币安平仓订单号(可选,用于对账) + + 注意:如果 exit_order_id 已存在且属于其他交易记录,会跳过更新 exit_order_id 以避免唯一约束冲突 """ exit_time = get_beijing_time() - db.execute_update( - """UPDATE trades - SET exit_price = %s, exit_time = %s, - exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed', - exit_order_id = %s - WHERE id = %s""", - (exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id, trade_id) - ) + + # 如果提供了 exit_order_id,先检查是否已被其他交易记录使用 + if exit_order_id is not None: + try: + existing_trade = Trade.get_by_exit_order_id(exit_order_id) + if existing_trade and existing_trade['id'] != trade_id: + # 如果 exit_order_id 已被其他交易记录使用,记录警告但不更新 exit_order_id + logger.warning( + f"交易记录 {trade_id} 的 exit_order_id {exit_order_id} 已被交易记录 {existing_trade['id']} 使用," + f"跳过更新 exit_order_id,只更新其他字段" + ) + # 只更新其他字段,不更新 exit_order_id + db.execute_update( + """UPDATE trades + SET exit_price = %s, exit_time = %s, + exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed' + WHERE id = %s""", + (exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id) + ) + return + except Exception as e: + # 如果查询失败,记录警告但继续正常更新 + logger.warning(f"检查 exit_order_id {exit_order_id} 时出错: {e},继续正常更新") + + # 正常更新(包括 exit_order_id) + try: + db.execute_update( + """UPDATE trades + SET exit_price = %s, exit_time = %s, + exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed', + exit_order_id = %s + WHERE id = %s""", + (exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id, trade_id) + ) + except Exception as e: + # 如果更新失败(可能是唯一约束冲突),尝试不更新 exit_order_id + error_str = str(e) + if "Duplicate entry" in error_str and "exit_order_id" in error_str: + logger.warning( + f"更新交易记录 {trade_id} 时 exit_order_id {exit_order_id} 唯一约束冲突," + f"尝试不更新 exit_order_id" + ) + # 只更新其他字段,不更新 exit_order_id + db.execute_update( + """UPDATE trades + SET exit_price = %s, exit_time = %s, + exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed' + WHERE id = %s""", + (exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id) + ) + else: + # 其他错误,重新抛出 + raise @staticmethod def get_by_entry_order_id(entry_order_id):