需求场景:

有一业务数据库,使用MySQL 5.5版本,每天会写入大量数据,需要不定期将多表中“指定时期前“的数据进行删除,在SQL SERVER中很容易实现,写几个WHILE循环就搞定,虽然MySQL中也存在类似功能,怎奈自己不精通,于是采用Python来实现

话不多少,上脚本:

# coding: utf-8
import MySQLdb
import time
import os # delete config
# 如果VIEW_OR_RUN = "VIEW",仅生成小批量删除的脚本但不执行
# 如果VIEW_OR_RUN = "RUN",生成小批量删除的脚本并直接调用执行
VIEW_OR_RUN = "VIEW"
DELETE_DATABASE_NAME = ""
DELETE_TABLE_NAME = ""
DELETE_TABLE_KEY = ""
DELETE_CONDITION = ""
DELETE_ROWS_PER_BATCH = 10000
SLEEP_SECOND_PER_BATCH = 0.5 # MySQL Connection Config
Default_MySQL_Host = '192.168.166.169'
Default_MySQL_Port = 3358
Default_MySQL_User = "mysql_admin"
Default_MySQL_Password = 'mysql@Admin@Pwd' Default_MySQL_Charset = "utf8"
Default_MySQL_Connect_TimeOut = 120
Default_MySQL_Socket = "/export/data/mysql/tmp/mysql.sock" # Common config:
DATETIME_FORMAT = '%Y-%m-%d %X'
EXEC_DETAIL_FILE = 'exec_detail.txt'
EXEC_SCRIPT_FILE = 'delete_scripts.sql' def highlight(s):
return "%s[30;2m%s%s[1m" % (chr(27), s, chr(27)) def print_warning_message(message):
"""
以红色字体显示消息内容
:param message: 消息内容
:return: 无返回值
"""
message = str(message)
print(highlight('') + "%s[31;1m%s%s[0m" % (chr(27), message, chr(27)))
global EXEC_DETAIL_FILE
write_file(EXEC_DETAIL_FILE, message) def print_info_message(message):
"""
以绿色字体输出提醒性的消息
:param message: 消息内容
:return: 无返回值
"""
message = str(message)
print(highlight('') + "%s[32;2m%s%s[0m" % (chr(27), message, chr(27)))
global EXEC_DETAIL_FILE
write_file(EXEC_DETAIL_FILE, message) def write_file(file_path, message):
"""
将传入的message追加写入到file_path指定的文件中
请先创建文件所在的目录
:param file_path: 要写入的文件路径
:param message: 要写入的信息
:return:
"""
file_handle = open(file_path, 'a')
file_handle.writelines(message)
# 追加一个换行以方便浏览
file_handle.writelines(chr(13))
file_handle.close() def get_user_choose_option(input_options, input_message):
while_flag = True
choose_option = None
while while_flag:
print_info_message(input_message)
str_input = raw_input("")
for input_option in input_options:
if str_input.strip() == input_option:
choose_option = input_option
while_flag = False
return choose_option def get_mysql_connection():
"""
根据默认配置返回数据库连接
:return: 数据库连接
"""
if Default_MySQL_Host.lower() == 'localhost':
conn = MySQLdb.connect(
host=Default_MySQL_Host,
port=Default_MySQL_Port,
user=Default_MySQL_User,
passwd=Default_MySQL_Password,
connect_timeout=Default_MySQL_Connect_TimeOut,
charset=Default_MySQL_Charset,
db=DELETE_DATABASE_NAME,
unix_socket=Default_MySQL_Socket
)
else:
conn = MySQLdb.connect(
host=Default_MySQL_Host,
port=Default_MySQL_Port,
user=Default_MySQL_User,
passwd=Default_MySQL_Password,
connect_timeout=Default_MySQL_Connect_TimeOut,
charset=Default_MySQL_Charset,
db=DELETE_DATABASE_NAME
)
return conn def mysql_exec(sql_script, sql_param=None):
"""
执行传入的脚本,返回影响行数
:param sql_script:
:param sql_param:
:return: 脚本最后一条语句执行影响行数
"""
try:
conn = get_mysql_connection()
print_info_message("在服务器{0}上执行脚本:{1}".format(
conn.get_host_info(), sql_script))
cursor = conn.cursor()
if sql_param is not None:
cursor.execute(sql_script, sql_param)
else:
cursor.execute(sql_script)
affect_rows = cursor.rowcount
conn.commit()
cursor.close()
conn.close()
return affect_rows
except Exception as ex:
cursor.close()
conn.rollback()
raise Exception(str(ex)) def mysql_exec_many(sql_script_list):
"""
执行传入的脚本,返回影响行数
:param sql_script_list: 要执行的脚本List,List中每个元素为sql_script, sql_param对
:return: 返回执行每个脚本影响的行数列表
"""
try:
conn = get_mysql_connection()
exec_result_list = []
for sql_script, sql_param in sql_script_list:
print_info_message("在服务器{0}上执行脚本:{1}".format(
conn.get_host_info(), sql_script))
cursor = conn.cursor()
if sql_param is not None:
cursor.execute(sql_script, sql_param)
else:
cursor.execute(sql_script)
affect_rows = cursor.rowcount
exec_result_list.append("影响行数:{0}".format(affect_rows))
conn.commit()
cursor.close()
conn.close()
return exec_result_list except Exception as ex:
cursor.close()
conn.rollback()
raise Exception(str(ex)) def mysql_query(sql_script, sql_param=None):
"""
执行传入的SQL脚本,并返回查询结果
:param sql_script:
:param sql_param:
:return: 返回SQL查询结果
"""
try:
conn = get_mysql_connection()
print_info_message("在服务器{0}上执行脚本:{1}".format(
conn.get_host_info(), sql_script))
cursor = conn.cursor()
if sql_param is not None:
cursor.execute(sql_script, sql_param)
else:
cursor.execute(sql_script)
exec_result = cursor.fetchall()
cursor.close()
conn.close()
return exec_result
except Exception as ex:
cursor.close()
conn.close()
raise Exception(str(ex)) def get_column_info_list(table_name):
sql_script = """
DESC {0}
""".format(table_name)
column_info_list = []
query_result = mysql_query(sql_script=sql_script, sql_param=None)
for row in query_result:
column_name = row[0]
column_type = row[1]
column_key = row[3]
column_info = column_name, column_key, column_type
column_info_list.append(column_info)
return column_info_list def get_id_range():
"""
按照传入的表获取要删除数据最大ID、最小ID、删除总行数
:return: 返回要删除数据最大ID、最小ID、删除总行数
"""
global DELETE_TABLE_NAME
global DELETE_CONDITION
sql_script = """
SELECT
MAX({2}) AS MAX_ID,
MIN({2}) AS MIN_ID,
COUNT(1) AS Total_Count
FROM {0}
WHERE {1};
""".format(DELETE_TABLE_NAME, DELETE_CONDITION, DELETE_TABLE_KEY) query_result = mysql_query(sql_script=sql_script, sql_param=None)
max_id, min_id, total_count = query_result[0]
# 此处有一坑,可能出现total_count不为0 但是max_id 和min_id 为None的情况
# 因此判断max_id和min_id 是否为NULL
if (max_id is None) or (min_id is None):
max_id, min_id, total_count = 0, 0, 0
return max_id, min_id, total_count def delete_data(current_min_id, current_max_id):
sql_script = """
DELETE FROM {0}
WHERE {4} <= {1}
and {4} >= {2}
AND {3};
""".format(DELETE_TABLE_NAME,
current_max_id,
current_min_id,
DELETE_CONDITION,
DELETE_TABLE_KEY
)
global EXEC_SCRIPT_FILE
global VIEW_OR_RUN
if VIEW_OR_RUN == 'RUN':
row_count = mysql_exec(sql_script)
print_info_message("影响行数:{0}".format(row_count))
time.sleep(SLEEP_SECOND_PER_BATCH)
else:
print_info_message("生成删除脚本(未执行)")
print_info_message(sql_script)
tmp_script = """
USE {0};
""".format(DELETE_DATABASE_NAME) + sql_script + """
COMMIT;
SELECT SLEEP('{0}');
##=====================================================##
""".format(SLEEP_SECOND_PER_BATCH)
write_file(file_path=EXEC_SCRIPT_FILE, message=tmp_script) def loop_delete_data():
max_id, min_id, total_count = get_id_range()
if min_id == max_id:
print_info_message("无数据需要结转")
return
current_min_id = min_id
global DELETE_ROWS_PER_BATCH
while current_min_id <= max_id:
print_info_message("*" * 70)
current_max_id = current_min_id + DELETE_ROWS_PER_BATCH
delete_data(current_min_id, current_max_id)
current_percent = (current_max_id - min_id) * 100.0 / (max_id - min_id)
left_rows = max_id - current_max_id
if left_rows < 0:
left_rows = 0
current_percent_str = "%.2f" % current_percent
info = "当前进度{0}/{1},剩余{2},进度为{3}%"
info = info.format(current_max_id,
max_id,
left_rows,
current_percent_str)
print_info_message(info)
current_min_id = current_max_id
print_info_message("*" * 70)
print_info_message("执行完成") def check_config():
try:
global DELETE_DATABASE_NAME
global DELETE_TABLE_NAME
global DELETE_TABLE_KEY
global DELETE_CONDITION
global VIEW_OR_RUN if str(DELETE_DATABASE_NAME).strip() == "":
print_warning_message("数据库名不能为空")
return False
if str(DELETE_TABLE_NAME).strip() == "":
print_warning_message("表名不能为空")
return False
if str(DELETE_CONDITION).strip() == "":
print_warning_message("删除条件不能为空")
return False
source_columns_info_list = get_column_info_list(DELETE_TABLE_NAME)
column_count = len(source_columns_info_list)
primary_key_count = 0
for column_id in range(column_count):
source_column_name, source_column_key, source_column_type = source_columns_info_list[column_id]
if source_column_key.lower() == 'pri':
primary_key_count += 1
if not ('int' in str(source_column_type).lower()):
print_warning_message("主键不为int或bigint")
return False
else:
global DELETE_TABLE_KEY
DELETE_TABLE_KEY = source_column_name if primary_key_count == 0:
print_warning_message("未找到主键,不瞒足迁移条件")
return False if primary_key_count > 1:
print_warning_message("要删除的表使用复合主键,不满足迁移条件")
return False return True
except Exception as ex:
print_warning_message("执行出现异常,异常为{0}".format(ex.message))
return False def clean_env():
global DELETE_DATABASE_NAME
global DELETE_TABLE_NAME
global DELETE_TABLE_KEY
global DELETE_CONDITION
global VIEW_OR_RUN
DELETE_DATABASE_NAME = ""
DELETE_TABLE_NAME = ""
DELETE_TABLE_KEY = ""
DELETE_CONDITION = ""
VIEW_OR_RUN = "VIEW" if os.path.exists(EXEC_SCRIPT_FILE):
os.remove(EXEC_SCRIPT_FILE)
if os.path.exists(EXEC_DETAIL_FILE):
os.remove(EXEC_DETAIL_FILE) def user_confirm():
if VIEW_OR_RUN == 'RUN':
info = """
您将在服务器{0}上{1}中删除表{2}中数据 删除数据条件为:
DELETE FROM {3}
WHERE {4}
"""
else:
info = """
将生成在服务器{0}上{1}中删除表{2}中数据的脚本 删除数据条件为:
DELETE FROM {3}
WHERE {4}
"""
info = info.format(Default_MySQL_Host,
DELETE_DATABASE_NAME,
DELETE_TABLE_NAME,
DELETE_TABLE_NAME,
DELETE_CONDITION) if VIEW_OR_RUN == "RUN":
print_warning_message(info)
else:
print_info_message(info)
input_options = ['yes', 'no']
input_message = """
请输入yes继续或输入no退出,yes/no?
"""
user_option = get_user_choose_option(input_options=input_options,
input_message=input_message)
if user_option == "no":
return False
else:
return True def delete_table_data(database_name, table_name, delete_condition, is_run, is_need_confirm):
global DELETE_DATABASE_NAME
global DELETE_TABLE_NAME
global DELETE_TABLE_KEY
global DELETE_CONDITION
global VIEW_OR_RUN
DELETE_DATABASE_NAME = database_name
DELETE_TABLE_NAME = table_name
DELETE_CONDITION = delete_condition
DELETE_TABLE_KEY = ''
if is_run:
VIEW_OR_RUN = "RUN"
else:
VIEW_OR_RUN = "VIEW"
check_result = check_config()
if not check_result:
return
if is_need_confirm:
confirm_result = user_confirm()
else:
confirm_result = True
if confirm_result:
loop_delete_data() def main():
clean_env()
delete_table_data(database_name="db001",
table_name="tb2001",
delete_condition="dt<'2017-09-01'",
is_run=True,
is_need_confirm=True) if __name__ == '__main__':
main()

执行效果:

实现原理:

由于表存在自增ID,于是给我们增量循环删除的机会,查找出满足删除条件的最大值ID和最小值ID,然后按ID 依次递增,每次小范围内(如10000条)进行删除。

实现优点:

实现“小斧子砍大柴”的效果,事务小,对线上影响较小,打印出当前处理到的“ID”,可以随时关闭,稍微修改下代码便可以从该ID开始,方便。

实现不足:

为防止主从延迟太高,采用每次删除SLEEP1秒的方式,相对比较糙,最好的方式应该是周期扫描这条复制链路,根据延迟调整SLEEP的周期,反正都脚本化,再智能化点又何妨!

本文重点依旧是妹子,不能让诸位看官白跑一趟,是不!!!

Python--增量循环删除MySQL表数据的更多相关文章

  1. 工作随笔——mysql子查询删除原表数据

    最近在开发的时候遇到一个mysql的子查询删除原表数据的问题.在网上也看了很多方法,基本也是然并卵(不是写的太乱就是效率太慢). 公司DBA给了一个很好的解决方案,让人耳目一新. DELETE fb. ...

  2. php实例根据ID删除mysql表中的数据

    在动态网站开发中,我们经常要根据ID删除表中的数据,例如用户删除帖子,就需要根据ID删除帖子.本文章向大家介绍php根据ID删除表中数据的实例,需要的朋友可以参考一下本文章的实例. php实例根据ID ...

  3. MYSQL中delete删除多表数据

    MYSQL中delete删除多表数据 DELETE删除多表数据,怎样才能同时删除多个关联表的数据呢?这里做了深入的解释: 1. delete from t1 where 条件 2.delete t1 ...

  4. 利用Flume将MySQL表数据准实时抽取到HDFS

    转自:http://blog.csdn.net/wzy0623/article/details/73650053 一.为什么要用到Flume 在以前搭建HAWQ数据仓库实验环境时,我使用Sqoop抽取 ...

  5. 删除MySQL重复数据

    删除MySQL重复数据 项目背景 在最近做的一个linux性能采集项目中,发现线程的程序入库很慢,再仔细定位,发现数据库里面很多冗余数据.因为在采集中,对于同一台设备,同一个时间点应该只有一个数据,然 ...

  6. MySQL 表数据的导入导出

    数据导出 1.  使用 SELECT ...INTO OUTFILE ...命令来导出数据,具体语法如下. mysql> SELECT * FROM tablename INTO OUTFILE ...

  7. 如何实现MySQL表数据随机读取?从mysql表中读取随机数据

    文章转自 http://blog.efbase.org/2006/10/16/244/如何实现MySQL表数据随机读取?从mysql表中读取随机数据?以前在群里讨论过这个问题,比较的有意思.mysql ...

  8. sqlserver快速删除整个表数据

    --删除整个表数据 SET STATISTICS TIME ON; DECLARE @Timer DATETIME = GETDATE(); TRUNCATE TABLE LOG_DEBUG_ERRO ...

  9. MSSQL 删除数据库表数据

    --删除数据库表数据 慎用 create PROCEDURE sp_DeleteAllData AS ) ) ) ) ) ) begin try begin tran -- 失效索引,触发器 open ...

随机推荐

  1. link和import的区别

    1.link是xhtml标签,除了可以加载css外,还可以加载定义其他的事务:@import只属于css的范畴,只能加载css: 2.link引用CSS时,在页面载入时同时加载:@import需要页面 ...

  2. 20145229&20145316 《信息安全系统设计基础》 实验二 固件设计

    实验封面 实验步骤 1.配置环境 开发环境的配置同实验一 2.拷贝文件 将实验代码拷贝到共享文件夹中 3.在虚拟机中编译代码 4.下载调试 在超级终端中运行可执行文件pthread,可得实验结果如图 ...

  3. 谷歌CEO发布年度公开信:专注人工智能等6大业务领域

  4. Android应用:StatusBar状态栏、NavigationBar虚拟按键栏、ActionBar标题栏、Window屏幕内容区域等的宽高

    一.屏幕中各种栏目以及屏幕的尺寸 当我们需要计算屏幕中一些元素的高度时,或许需要先获取到屏幕或者各种栏目的高度,下面这个类包含了Status bar状态栏,Navigation bar虚拟按键栏,Ac ...

  5. React Native填坑之旅--Flow篇(番外)

    flow不是React Native必会的技能,但是作为正式的产品开发优势很有必要掌握的技能之一.所以,算是RN填坑之旅系列的番外篇. Flow是一个静态的检查类型检查工具,设计之初的目的就是为了可以 ...

  6. React Native填坑之旅--重新认识RN

    如同黑夜里的一道光一样,就这么知道了F8. F8是每年一次Facebook每年一次的开发者大会.每次大会都会release相应的APP,iOS.Android都有.之前都是用Native开发的,但是2 ...

  7. [Leetcode][JAVA] Convert Sorted Array to Binary Search Tree && Convert Sorted List to Binary Search Tree

    Convert Sorted Array to Binary Search Tree Given an array where elements are sorted in ascending ord ...

  8. NGUI 3.0.7的新锚点系统设置不好就会造成显示错误的错觉

    每次设置NGUI控件的锚点时,都需要刷新一下窗口,不然就会造成显示错误的错觉. 同时,NGUI控件设置锚点的参考对象为一个物体时还需要调整其控件大小,不然也会造成显示错误的错觉.

  9. SQL 常用语句

    替换表中某个字段中的某些字符:(将ConfigValue列中的A值替换为B) update SysConfigParams set ConfigValue=replace(ConfigValue,'A ...

  10. 装个蒜。学习下dispatch queue

    dispatch queue的真髓:能串行,能并行,能同步,能异步以及共享同一个线程池. 接口: GCD是基于C语言的APT.虽然最新的系统版本中GCD对象已经转成了Objective-C对象,但AP ...