需求场景:

有一业务数据库,使用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. asp.net大文件上传与上传文件进度条问题

    利用Plupload解决大容量文件上传问题, 带进度条和背景遮罩层 关于Plupload结合上传插件jquery.plupload.queue的使用 这是群里面一位朋友给的资料. 下面是自己搜索到的一 ...

  2. php生成网页桌面快捷方式

    本文将介绍使用PHP生成网页桌面快捷方式的代码,并添加图标及解决不同浏览器保存出现的乱码问题. 我们访问网站时,如果网站的内容很有吸引,一般我们都会使用浏览器的收藏夹功能,收藏此网站. 在浏览器收藏的 ...

  3. JSNO

    JSON 编辑 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. JSON采用完全独立于语言的文本格式,但是也使用了 ...

  4. linux 项目环境搭建配置

    经过三天加一上午的努力折腾,本地项目终于跑起来了,linux系统,重装后需要安装基本的php,nginx,mysql.php扩展需要安装curl ,memcache,memcached等.然后就是修改 ...

  5. Git 源代码管理工具

    Git 源代码管理工具 Git基本信息 Git :Git是一个“分布式”的版本控制工具 Git的作者是Linux之父 Linus Benedict Torvolds,当初开发Git仅仅是为了辅助Lin ...

  6. jQuery基础_3

    DOM:文档处理内部插入:父子级关系$("a").append($("b"))把b插入到a中[a里面的后面]$("b").appendTo( ...

  7. 【转】如何在Windows+VS2005使用最新静态libcurl 7.35.0获取网页数据,支持HTTPS

    地址: http://blog.csdn.net/hujkay作者:Jekkay Hu(34538980@qq.com)关键词:Windows,curl,ssl,  visual c++ 2005, ...

  8. 8天入门wpf(转)

    8天入门wpf—— 第一天 基础概念介绍 8天入门wpf—— 第二天 xaml详解 8天入门wpf—— 第三天 样式 8天入门wpf—— 第四天 模板 8天入门wpf—— 第五天 数据绑定 8天入门w ...

  9. Red Hat Enterprise Server 5.8+oracle10g(中文界面)安装

    Red Hat Enterprise Server 5.8+oracle10g(中文界面)安装 VMware workstation10(虚拟机)下面安装红帽企业版5.8 创建虚拟机 新建虚拟机,选择 ...

  10. iOS原生地图开发指南续——大头针与自定义标注

    iOS原生地图开发指南续——大头针与自定义标注 出自:http://www.sxt.cn/info-6042-u-7372.html 在上一篇博客中http://my.oschina.net/u/23 ...