clean_zones_and_fix_uk.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. # -*- coding: utf-8 -*-
  2. """清理 zones 重复行 + 把 UNIQUE KEY 改成 (server_id, zone_id, open_date)。
  3. - 旧唯一键 (server_id, zone_id, open_time) 在 B2 bug 期间产生了重复行
  4. (同一 zone_id 一天两个 open_time: 真实开服时间 vs DEFAULT NOW())
  5. - 改键后: 每天每个 zone_id 一行
  6. - 步骤: 删旧脏行 -> 改键 -> 验证
  7. """
  8. import pymysql
  9. CONN = dict(host="47.99.164.66", port=3306, user="root",
  10. password="hn02le.34lkdLKD", database="996cq",
  11. charset="utf8mb4", autocommit=True, connect_timeout=10)
  12. DRY = False # 改成 True 试运行
  13. def main():
  14. conn = pymysql.connect(**CONN)
  15. try:
  16. with conn.cursor() as cur:
  17. print("== 1. 找重复行 (同一 server_id+zone_id+date 多行) ==")
  18. cur.execute("""
  19. SELECT server_id, zone_id, open_date, COUNT(*) cnt
  20. FROM zones
  21. GROUP BY server_id, zone_id, open_date
  22. HAVING cnt > 1
  23. """)
  24. dups = cur.fetchall()
  25. print(f" {len(dups)} 个 (server,zone,date) 组合存在重复")
  26. for sid, zid, od, cnt in dups[:20]:
  27. cur.execute("""
  28. SELECT id, open_time, status, api_state FROM zones
  29. WHERE server_id=%s AND zone_id=%s AND open_date=%s
  30. ORDER BY id
  31. """, (sid, zid, od))
  32. print(f" server={sid} zone_id={zid} date={od}:")
  33. for r in cur.fetchall():
  34. print(f" id={r[0]:<3} open_time={r[1]} status={r[2]} api_state={r[3]}")
  35. if not dups:
  36. print(" (无重复, 跳过清理)")
  37. else:
  38. # 保留每组中 id 最大的(最后写入,字段最新), 删 id 小的脏行
  39. to_delete = []
  40. for sid, zid, od, cnt in dups:
  41. cur.execute("""
  42. SELECT id, open_time, api_state, fetched_at FROM zones
  43. WHERE server_id=%s AND zone_id=%s AND open_date=%s
  44. ORDER BY id DESC
  45. """, (sid, zid, od))
  46. rows = cur.fetchall()
  47. keep = rows[0] # 最新
  48. for r in rows[1:]:
  49. to_delete.append((r[0], sid, zid, od, r[1], keep[0]))
  50. print(f"\n== 2. 将删除 {len(to_delete)} 条脏行 (保留 id 最大的) ==")
  51. for did, sid, zid, od, ot, keep_id in to_delete:
  52. print(f" delete id={did} (server={sid} zone={zid} date={od} open_time={ot}, keeping id={keep_id})")
  53. if not DRY:
  54. cur.executemany(
  55. "DELETE FROM zones WHERE id = %s",
  56. [(d[0],) for d in to_delete])
  57. print("\n== 3. 改 UNIQUE KEY -> (server_id, zone_id, open_date) ==")
  58. cur.execute("SHOW INDEX FROM zones")
  59. idx = cur.fetchall()
  60. uniq = [r for r in idx if r[2] == "uniq_zone"]
  61. for r in uniq:
  62. print(f" 当前: key={r[2]} col={r[4]}")
  63. if not DRY:
  64. cur.execute("ALTER TABLE zones DROP INDEX uniq_zone")
  65. cur.execute("""
  66. ALTER TABLE zones
  67. ADD UNIQUE KEY uk_server_zone_date (server_id, zone_id, open_date)
  68. """)
  69. print(" 已替换为 uk_server_zone_date")
  70. if not DRY:
  71. print("\n== 4. 验证: 重复行 = 0, 当天记录 = 9 ==")
  72. cur.execute("""
  73. SELECT COUNT(*) FROM (
  74. SELECT server_id, zone_id, open_date, COUNT(*) cnt
  75. FROM zones GROUP BY server_id, zone_id, open_date HAVING cnt>1
  76. ) t
  77. """)
  78. print(f" 重复组合数: {cur.fetchone()[0]}")
  79. cur.execute("""
  80. SELECT COUNT(*) FROM zones WHERE server_id=8 AND open_date='2026-09-17'
  81. """)
  82. print(f" server=8, date=2026-09-17 行数: {cur.fetchone()[0]}")
  83. print("\n today zones (干净后):")
  84. cur.execute("""
  85. SELECT id, zone_id, zone_name, open_time, open_time_of_day, api_state, status
  86. FROM zones WHERE server_id=8 AND open_date='2026-09-17'
  87. ORDER BY open_time
  88. """)
  89. for r in cur.fetchall():
  90. print(f" id={r[0]:<3} {r[1]} {r[2]:<22} {r[3]} time={r[4]} state={r[5]} status={r[6]}")
  91. finally:
  92. conn.close()
  93. if __name__ == "__main__":
  94. main()