feat: 根目录文档、脚本、gitignore
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""200 zentao_dev 只读导出 → 161 zentao_dev_2026 导入
|
||||
源库只执行 SELECT/SHOW,绝不写入;流式游标 + 分批插入。
|
||||
"""
|
||||
import io, sys, time, re, pymysql
|
||||
from pymysql.cursors import SSCursor
|
||||
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
|
||||
SRC = dict(host='192.168.3.200', user='root', password='PX4fTAAsJ#T!1',
|
||||
database='zentao_dev', charset='utf8mb4', connect_timeout=15,
|
||||
read_timeout=900, write_timeout=900)
|
||||
DST = dict(host='192.168.1.161', user='devgps', password='dev@2021GPS',
|
||||
charset='utf8mb4', autocommit=False, connect_timeout=15,
|
||||
read_timeout=900, write_timeout=900)
|
||||
TARGET_DB = 'zentao_dev_2026'
|
||||
BATCH = 500
|
||||
# 日志大表只建结构不拷数据(用户拍板 2026-07-29)
|
||||
SKIP_DATA = {'zt_action', 'zt_actionrecent'}
|
||||
|
||||
def log(msg):
|
||||
print(f'[{time.strftime("%H:%M:%S")}] {msg}', flush=True)
|
||||
|
||||
src = pymysql.connect(**SRC)
|
||||
dst = pymysql.connect(**DST)
|
||||
scur = src.cursor()
|
||||
dcur = dst.cursor()
|
||||
|
||||
# 目标库会话加速+防约束干扰+放大单包上限
|
||||
dcur.execute('SET SESSION foreign_key_checks=0')
|
||||
dcur.execute('SET SESSION unique_checks=0')
|
||||
try:
|
||||
dcur.execute('SET SESSION max_allowed_packet=1073741824')
|
||||
except Exception:
|
||||
pass
|
||||
dcur.execute(f'CREATE DATABASE IF NOT EXISTS `{TARGET_DB}` DEFAULT CHARACTER SET utf8mb4')
|
||||
dcur.execute(f'USE `{TARGET_DB}`')
|
||||
dst.commit()
|
||||
log(f'目标库 {TARGET_DB} 就绪')
|
||||
|
||||
# 源库表清单(仅 BASE TABLE)
|
||||
scur.execute("SELECT table_name FROM information_schema.tables WHERE table_schema='zentao_dev' AND table_type='BASE TABLE' ORDER BY table_name")
|
||||
tables = [r[0] for r in scur.fetchall()]
|
||||
log(f'共 {len(tables)} 张表')
|
||||
|
||||
# 1) 建表结构(只建目标库缺失的表;已存在的不动——配合断点续跑,已拷数据不丢)
|
||||
dcur.execute("SELECT table_name FROM information_schema.tables WHERE table_schema=%s", (TARGET_DB,))
|
||||
existing = {r[0] for r in dcur.fetchall()}
|
||||
created = 0
|
||||
for t in tables:
|
||||
if t in existing:
|
||||
continue
|
||||
scur.execute(f'SHOW CREATE TABLE `{t}`')
|
||||
create_sql = scur.fetchone()[1]
|
||||
create_sql = re.sub(r' DEFINER=`[^`]+`@`[^`]+`', '', create_sql)
|
||||
dcur.execute(create_sql)
|
||||
created += 1
|
||||
dst.commit()
|
||||
log(f'表结构就绪(新建 {created} 张,复用 {len(tables)-created} 张)')
|
||||
|
||||
def row_size(row):
|
||||
s = 0
|
||||
for v in row:
|
||||
if v is None:
|
||||
continue
|
||||
if isinstance(v, (bytes, bytearray)):
|
||||
s += len(v)
|
||||
elif isinstance(v, str):
|
||||
s += len(v.encode('utf-8', 'ignore'))
|
||||
else:
|
||||
s += 16
|
||||
return s
|
||||
|
||||
MAX_PACKET = 12 * 1024 * 1024 # 单包限 12MB(161 max_allowed_packet=64MB,留足余量)
|
||||
|
||||
def copy_table(t, attempt):
|
||||
"""单表复制:断线重连 + 按字节分包;重试前清空目标表保证幂等"""
|
||||
global src, dst, scur, dcur
|
||||
if attempt > 0:
|
||||
time.sleep(3)
|
||||
for conn in (src, dst):
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
src = pymysql.connect(**SRC)
|
||||
dst = pymysql.connect(**DST)
|
||||
scur = src.cursor()
|
||||
dcur = dst.cursor()
|
||||
dcur.execute('SET SESSION foreign_key_checks=0')
|
||||
dcur.execute('SET SESSION unique_checks=0')
|
||||
try:
|
||||
dcur.execute('SET SESSION max_allowed_packet=1073741824')
|
||||
except Exception:
|
||||
pass
|
||||
dcur.execute(f'USE `{TARGET_DB}`')
|
||||
dst.commit()
|
||||
dcur.execute(f'DELETE FROM `{t}`')
|
||||
dst.commit()
|
||||
read_cur = src.cursor(SSCursor)
|
||||
read_cur.execute(f'SELECT * FROM `{t}`')
|
||||
cols = [d[0] for d in read_cur.description]
|
||||
col_sql = ','.join(f'`{c}`' for c in cols)
|
||||
ph = ','.join(['%s'] * len(cols))
|
||||
insert_sql = f'INSERT INTO `{t}` ({col_sql}) VALUES ({ph})'
|
||||
n = 0
|
||||
batch = []
|
||||
batch_bytes = 0
|
||||
for row in read_cur:
|
||||
batch.append(row)
|
||||
batch_bytes += row_size(row)
|
||||
if len(batch) >= BATCH or batch_bytes >= MAX_PACKET:
|
||||
dcur.executemany(insert_sql, batch)
|
||||
dst.commit()
|
||||
n += len(batch)
|
||||
batch = []
|
||||
batch_bytes = 0
|
||||
if batch:
|
||||
dcur.executemany(insert_sql, batch)
|
||||
dst.commit()
|
||||
n += len(batch)
|
||||
read_cur.close()
|
||||
return n
|
||||
|
||||
# 2) 逐表复制数据(源只读流式;SKIP_DATA 只留空表结构;已拷完的表自动跳过=断点续跑)
|
||||
total_rows = 0
|
||||
for t in tables:
|
||||
if t in SKIP_DATA:
|
||||
log(f'{t}: 跳过数据(仅结构,0 行)')
|
||||
continue
|
||||
scur.execute(f'SELECT COUNT(*) FROM `{t}`')
|
||||
src_cnt = scur.fetchone()[0]
|
||||
dcur.execute(f'SELECT COUNT(*) FROM `{t}`')
|
||||
if dcur.fetchone()[0] == src_cnt:
|
||||
log(f'{t}: 已拷过({src_cnt} 行),续跑跳过')
|
||||
total_rows += src_cnt
|
||||
continue
|
||||
t0 = time.time()
|
||||
last_err = None
|
||||
for attempt in range(3):
|
||||
try:
|
||||
n = copy_table(t, attempt)
|
||||
break
|
||||
except pymysql.err.OperationalError as e:
|
||||
last_err = e
|
||||
log(f'{t}: 第 {attempt+1} 次失败 {e},重连重试')
|
||||
else:
|
||||
raise last_err
|
||||
total_rows += n
|
||||
log(f'{t}: {n} 行 ({time.time()-t0:.0f}s)')
|
||||
log(f'数据复制完成,总 {total_rows} 行')
|
||||
|
||||
# 3) 逐表行数对拍(SKIP_DATA 预期 0 行,单独标注)
|
||||
mismatch = []
|
||||
for t in tables:
|
||||
scur.execute(f'SELECT COUNT(*) FROM `{t}`')
|
||||
s = scur.fetchone()[0]
|
||||
dcur.execute(f'SELECT COUNT(*) FROM `{t}`')
|
||||
d = dcur.fetchone()[0]
|
||||
if t in SKIP_DATA:
|
||||
log(f'{t}: 源 {s} 行 → 目标 {d} 行(按计划跳过)')
|
||||
elif s != d:
|
||||
mismatch.append((t, s, d))
|
||||
if mismatch:
|
||||
log(f'!!! 行数不一致 {len(mismatch)} 张: {mismatch}')
|
||||
else:
|
||||
log(f'对拍通过:{len(tables)} 张表行数全部一致')
|
||||
|
||||
# 4) 抽查中文往返
|
||||
dcur.execute('SELECT id, title FROM zt_story WHERE id=6566')
|
||||
log(f'抽查 zt_story 6566: {dcur.fetchone()}')
|
||||
dcur.execute('SELECT COUNT(*) FROM zt_perf_config')
|
||||
log(f'抽查 zt_perf_config 行数: {dcur.fetchone()[0]}')
|
||||
src.close(); dst.close()
|
||||
log('全部完成')
|
||||
Reference in New Issue
Block a user