Files
exchange_monitor_sync/utils/batch_order_sync.py
lz_db 803d40b88e 1
2025-12-03 14:40:14 +08:00

313 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import List, Dict, Any, Tuple
from loguru import logger
from sqlalchemy import text
import time
class BatchOrderSync:
"""订单数据批量同步工具(最高性能)"""
def __init__(self, db_manager, batch_size: int = 1000):
self.db_manager = db_manager
self.batch_size = batch_size
def sync_orders_batch(self, all_orders: List[Dict]) -> Tuple[bool, int]:
"""批量同步订单数据"""
if not all_orders:
return True, 0
session = self.db_manager.get_session()
try:
start_time = time.time()
# 方法1使用临时表性能最好
processed_count = self._sync_using_temp_table(session, all_orders)
elapsed = time.time() - start_time
logger.info(f"订单批量同步完成: 处理 {processed_count} 条订单,耗时 {elapsed:.2f}")
return True, processed_count
except Exception as e:
logger.error(f"订单批量同步失败: {e}")
return False, 0
finally:
session.close()
def _sync_using_temp_table(self, session, all_orders: List[Dict]) -> int:
"""使用临时表批量同步订单"""
try:
# 1. 创建临时表
session.execute(text("""
CREATE TEMPORARY TABLE IF NOT EXISTS temp_orders (
st_id INT,
k_id INT,
asset VARCHAR(32),
order_id VARCHAR(765),
symbol VARCHAR(120),
side VARCHAR(120),
price FLOAT,
time INT,
order_qty FLOAT,
last_qty FLOAT,
avg_price FLOAT,
exchange_id INT,
UNIQUE KEY idx_unique_order (order_id, symbol, k_id, side)
)
"""))
# 2. 清空临时表
session.execute(text("TRUNCATE TABLE temp_orders"))
# 3. 批量插入数据到临时表(分块)
inserted_count = self._batch_insert_to_temp_table(session, all_orders)
if inserted_count == 0:
session.execute(text("DROP TEMPORARY TABLE IF EXISTS temp_orders"))
return 0
# 4. 使用临时表更新主表
# 更新已存在的记录(只更新需要比较的字段)
update_result = session.execute(text("""
UPDATE deh_strategy_order_new main
INNER JOIN temp_orders temp
ON main.order_id = temp.order_id
AND main.symbol = temp.symbol
AND main.k_id = temp.k_id
AND main.side = temp.side
SET main.side = temp.side,
main.price = temp.price,
main.time = temp.time,
main.order_qty = temp.order_qty,
main.last_qty = temp.last_qty,
main.avg_price = temp.avg_price
WHERE main.side != temp.side
OR main.price != temp.price
OR main.time != temp.time
OR main.order_qty != temp.order_qty
OR main.last_qty != temp.last_qty
OR main.avg_price != temp.avg_price
"""))
updated_count = update_result.rowcount
# 插入新记录
insert_result = session.execute(text("""
INSERT INTO deh_strategy_order_new
(st_id, k_id, asset, order_id, symbol, side, price, time,
order_qty, last_qty, avg_price, exchange_id)
SELECT
st_id, k_id, asset, order_id, symbol, side, price, time,
order_qty, last_qty, avg_price, exchange_id
FROM temp_orders temp
WHERE NOT EXISTS (
SELECT 1 FROM deh_strategy_order_new main
WHERE main.order_id = temp.order_id
AND main.symbol = temp.symbol
AND main.k_id = temp.k_id
AND main.side = temp.side
)
"""))
inserted_count = insert_result.rowcount
# 5. 删除临时表
session.execute(text("DROP TEMPORARY TABLE IF EXISTS temp_orders"))
session.commit()
total_processed = updated_count + inserted_count
logger.info(f"订单批量同步: 更新 {updated_count} 条,插入 {inserted_count}")
return total_processed
except Exception as e:
session.rollback()
logger.error(f"临时表同步订单失败: {e}")
raise
def _batch_insert_to_temp_table(self, session, all_orders: List[Dict]) -> int:
"""批量插入数据到临时表"""
total_inserted = 0
try:
# 分块处理
for i in range(0, len(all_orders), self.batch_size):
chunk = all_orders[i:i + self.batch_size]
values_list = []
for order in chunk:
try:
# 处理NULL值
price = order.get('price')
time_val = order.get('time')
order_qty = order.get('order_qty')
last_qty = order.get('last_qty')
avg_price = order.get('avg_price')
# 转义单引号
symbol = order.get('symbol').replace("'", "''") if order.get('symbol') else ''
order_id = order.get('order_id').replace("'", "''") if order.get('order_id') else ''
values = (
f"({order['st_id']}, {order['k_id']}, '{order.get('asset', 'USDT')}', "
f"'{order_id}', "
f"'{symbol}', "
f"'{order['side']}', "
f"{price if price is not None else 'NULL'}, "
f"{time_val if time_val is not None else 'NULL'}, "
f"{order_qty if order_qty is not None else 'NULL'}, "
f"{last_qty if last_qty is not None else 'NULL'}, "
f"{avg_price if avg_price is not None else 'NULL'}, "
"NULL)"
)
values_list.append(values)
except Exception as e:
logger.error(f"构建订单值失败: {order}, error={e}")
continue
if values_list:
values_str = ", ".join(values_list)
sql = f"""
INSERT INTO temp_orders
(st_id, k_id, asset, order_id, symbol, side, price, time,
order_qty, last_qty, avg_price, exchange_id)
VALUES {values_str}
"""
result = session.execute(text(sql))
total_inserted += len(chunk)
return total_inserted
except Exception as e:
logger.error(f"批量插入临时表失败: {e}")
raise
def _batch_insert_to_temp_table1(self, session, all_orders: List[Dict]) -> int:
"""批量插入数据到临时表使用参数化查询temp_orders"""
total_inserted = 0
try:
# 分块处理
for i in range(0, len(all_orders), self.batch_size):
chunk = all_orders[i:i + self.batch_size]
# 准备参数化数据
insert_data = []
for order in chunk:
try:
insert_data.append({
'st_id': order['st_id'],
'k_id': order['k_id'],
'asset': order.get('asset', 'USDT'),
'order_id': order['order_id'],
'symbol': order['symbol'],
'side': order['side'],
'price': order.get('price'),
'time': order.get('time'),
'order_qty': order.get('order_qty'),
'last_qty': order.get('last_qty'),
'avg_price': order.get('avg_price')
# exchange_id 留空使用默认值NULL
})
except KeyError as e:
logger.error(f"订单数据缺少必要字段: {order}, missing={e}")
continue
except Exception as e:
logger.error(f"处理订单数据失败: {order}, error={e}")
continue
if insert_data:
sql = text(f"""
INSERT INTO {self.temp_table_name}
(st_id, k_id, asset, order_id, symbol, side, price, time,
order_qty, last_qty, avg_price)
VALUES
(:st_id, :k_id, :asset, :order_id, :symbol, :side, :price, :time,
:order_qty, :last_qty, :avg_price)
""")
try:
session.execute(sql, insert_data)
session.commit()
total_inserted += len(insert_data)
logger.debug(f"插入 {len(insert_data)} 条数据到临时表")
except Exception as e:
session.rollback()
logger.error(f"执行批量插入失败: {e}")
raise
logger.info(f"总共插入 {total_inserted} 条数据到临时表")
return total_inserted
except Exception as e:
logger.error(f"批量插入临时表失败: {e}")
session.rollback()
raise
def _sync_using_on_duplicate(self, session, all_orders: List[Dict]) -> int:
"""使用ON DUPLICATE KEY UPDATE批量同步简化版"""
try:
total_processed = 0
# 分块执行
for i in range(0, len(all_orders), self.batch_size):
chunk = all_orders[i:i + self.batch_size]
values_list = []
for order in chunk:
try:
# 处理NULL值
price = order.get('price')
time_val = order.get('time')
order_qty = order.get('order_qty')
last_qty = order.get('last_qty')
avg_price = order.get('avg_price')
symbol = order.get('symbol').replace("'", "''") if order.get('symbol') else ''
order_id = order.get('order_id').replace("'", "''") if order.get('order_id') else ''
values = (
f"({order['st_id']}, {order['k_id']}, '{order.get('asset', 'USDT')}', "
f"'{order_id}', "
f"'{symbol}', "
f"'{order['side']}', "
f"{price if price is not None else 'NULL'}, "
f"{time_val if time_val is not None else 'NULL'}, "
f"{order_qty if order_qty is not None else 'NULL'}, "
f"{last_qty if last_qty is not None else 'NULL'}, "
f"{avg_price if avg_price is not None else 'NULL'}, "
"NULL)"
)
values_list.append(values)
except Exception as e:
logger.error(f"构建订单值失败: {order}, error={e}")
continue
if values_list:
values_str = ", ".join(values_list)
sql = f"""
INSERT INTO deh_strategy_order_new
(st_id, k_id, asset, order_id, symbol, side, price, time,
order_qty, last_qty, avg_price, exchange_id)
VALUES {values_str}
ON DUPLICATE KEY UPDATE
side = VALUES(side),
price = VALUES(price),
time = VALUES(time),
order_qty = VALUES(order_qty),
last_qty = VALUES(last_qty),
avg_price = VALUES(avg_price)
"""
session.execute(text(sql))
total_processed += len(chunk)
session.commit()
return total_processed
except Exception as e:
session.rollback()
logger.error(f"ON DUPLICATE同步订单失败: {e}")
raise