自动监控MySQL表结构变更脚本

如何监控MySQL数据库表结构和表变更,并通知到相关的联系人、实现报警或通知?

由于平台采用django系统实现,因此通过如下代码实现(代码low,也可以写成python文件,传参数执行):
简单思路:
对用户指定库的所有列值进行md5,并存储到本地数据库,每次定时执行,校对md5,并找出不匹配的进行判断
会自动找出新增、删除、变更表结构的表

# models.py

class MonitorSchema(models.Model):
    table_schema = models.CharField(null=False, max_length=512)
    table_name = models.CharField(null=False, max_length=512)
    table_stru = models.TextField(null=False, default='')
    md5_sum = models.CharField(null=False, max_length=256)

    class Meta:
        verbose_name = u'监控表结构变更表'
        verbose_name_plural = verbose_name
        permissions = ()
        db_table = "dbaudit_monitor_schema"

# tasks.py

import datetime
import hashlib
import difflib

import mysql.connector as mdb
from celery import shared_task
from django.core.mail import EmailMessage
from django.template.loader import render_to_string

from auditdb.settings import EMAIL_FROM

@shared_task
def schema_modify_monitor(**kwargs):
    check_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    conn = connect_db(**kwargs)
    cursor = conn.cursor(dictionary=True)

    query_info = "select table_schema,table_name,group_concat(COLUMN_NAME) as column_name," \
                 "group_concat(COLUMN_DEFAULT) as column_default,group_concat(IS_NULLABLE) as is_nullable," \
                 "group_concat(DATA_TYPE) as data_type,group_concat(CHARACTER_MAXIMUM_LENGTH) as char_length," \
                 "group_concat(COLUMN_TYPE) as column_type,group_concat(COLUMN_COMMENT) as column_comment " \
                 "from columns where table_schema='{schema}' " \
                 "group by table_schema,table_name".format(schema=kwargs['schema'])

    cursor.execute(query_info)

    source_info = []
    table_list = []
    diff_old_data = ''
    diff_new_data = ''
    table_change_data = []

    for row in cursor.fetchall():
        table_schema = row['table_schema']
        table_name = row['table_name']

        md5_source = ''.join(str(row.values()))
        md5_sum = hashlib.md5(md5_source.encode('utf8')).hexdigest()
        source_info.append({'table_schema': table_schema, 'table_name': table_name, 'md5_sum': md5_sum})
        table_list.append(table_name)

    # 如果当前库没有记录,则进行初始化全量同步
    if MonitorSchema.objects.filter(table_schema=kwargs['schema']).first() is None:
        for row in source_info:
            table_schema = row['table_schema']
            table_name = row['table_name']

            query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))
            cursor.execute(query_table_stru)
            for i in cursor:
                table_stru = i['Create Table']
                row['table_stru'] = str(table_stru)
                MonitorSchema.objects.create(**row)
    else:
        # 如果存在,开始核验数据
        old_data = list(MonitorSchema.objects.filter(table_schema=kwargs['schema']).values_list('table_name', flat=True))
        new_data = table_list

        # 找出已删除的表,并处理
        table_remove = list(set(old_data).difference(set(new_data)))
        if table_remove:
            table_change_data.append({'remove': table_remove})
            # 从本地库中删除该表的记录
            MonitorSchema.objects.filter(table_schema=kwargs['schema']).filter(table_name__in=table_remove).delete()

        # 找出新增的表,并处理
        table_add = list(set(new_data).difference(set(old_data)))
        if table_add:
            for i in table_add:
                for j in source_info:
                    if i in j.values():
                        table_change_data.append({'add': j})
                        table_schema = j['table_schema']
                        table_name = j['table_name']
                        query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))
                        cursor.execute(query_table_stru)
                        for x in cursor:
                            table_stru = x['Create Table']
                            j['table_stru'] = str(table_stru)
                            MonitorSchema.objects.create(**j)

        # 找出相同的表,并核验表结构
        table_intersection = list(set(old_data).intersection(set(new_data)))
        for row in source_info:
            table_schema = row['table_schema']
            table_name = row['table_name']
            new_md5_sum = row['md5_sum']

            if table_name in table_intersection:
                old_table = MonitorSchema.objects.get(table_schema=table_schema, table_name=table_name)
                if new_md5_sum != old_table.md5_sum:
                    query_table_stru = "show create table {}".format('.'.join((table_schema, table_name)))
                    cursor.execute(query_table_stru)
                    for i in cursor:
                        table_stru = i['Create Table']
                        diff_old_data += old_table.table_stru + '\n'*3
                        diff_new_data += table_stru + '\n'*3
                        # 更新新表表结构到本地
                        MonitorSchema.objects.update_or_create(table_schema=table_schema, table_name=table_name,
                                                               defaults={'table_stru': table_stru,
                                                                         'md5_sum': new_md5_sum})

    if (diff_old_data and diff_new_data) or table_change_data:
        html_data = ''
        if diff_old_data and diff_new_data:
            diff_data = difflib.HtmlDiff(tabsize=2)
            old_table_stru = list(diff_old_data.split('\n'))
            new_table_stru = list(diff_new_data.split('\n'))
            html_data = diff_data.make_file(old_table_stru, new_table_stru, '旧表-表结构', '新表-表结构', context=False,
                                            numlines=5)

        email_html_body = render_to_string('_monitor_table.html', {'html_data': html_data, 'table_change_data': table_change_data})
        title = '{db}库表变更[来自:{host},检测时间:{check_time}]'.format(db=kwargs['schema'], host=kwargs['describle'], check_time=check_time)
        msg = EmailMessage(subject=title,
                           body=email_html_body,
                           from_email=EMAIL_FROM,
                           to=kwargs['receiver'].split(','),
                           )
        msg.content_subtype = "html"
        msg.send()
    cursor.close()
    conn.close()

对应的html文件:

# _monitor_table.html

<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <style>
        body {
            font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
            font-size: 12px;
            line-height: 1.42857143;
            color: #333;
        }

        .box.box-primary {
            border-top-color: #3c8dbc;
        }

        .box {
            position: relative;
            border-radius: 3px;
            background: #ffffff;
            border-top: 3px solid #d2d6de;
            margin-bottom: 20px;
            width: 100%;
            box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
        }

        .panel-danger > .panel-heading {
            color: #a94442;
            background-color: #f2dede;
            border-color: #ebccd1;
        }

        .panel-info > .panel-heading {
            color: #31708f;
            background-color: #d9edf7;
            border-color: #bce8f1;
        }

        .panel-success > .panel-heading {
            color: #3c763d;
            background-color: #dff0d8;
            border-color: #d6e9c6;
        }

        .panel-heading {
            padding: 6px 8px;
            border-bottom: 1px solid transparent;
            border-top-left-radius: 3px;
            border-top-right-radius: 3px;
        }

        .panel-body {
            padding: 6px;
            color: #3c763d;
            background-color: #f5f5f5;
        }
    </style>
</head>
<body>
<div class="box box-primary">
    <p>各位同仁好:</p>
    <p>  表结构变更如下,请查阅,谢谢。</p>

    {% if table_change_data %}
        {% for row in table_change_data %}
            {% if row.remove %}
                <div class="panel panel-danger">
                    <div class="panel-heading">删除的表</div>
                    <div class="panel-body">
                        {% for j in row.remove %}
                            {{ j }}
                        {% endfor %}
                    </div>
                </div>
            {% endif %}
        {% endfor %}

        {% for row in table_change_data %}
            {% if row.add %}
                <div class="panel panel-info">
                    <div class="panel-heading">新增的表:{{ row.add.table_name }}_[表结构]</div>
                    <div class="panel-body">
                        <pre>{{ row.add.table_stru }}</pre>
                    </div>
                </div>
            {% endif %}
        {% endfor %}
    {% endif %}

    {% if html_data %}
        <div class="panel panel-success">
            <div class="panel-heading">变更的表结构[左侧为变更前表结构、右侧为变更后表结构、标色部分为差异]</div>
            <div class="panel-body">
                {{ html_data|safe }}
            </div>
        </div>
    {% endif %}

</div>
</body>
</html>

最后在django后台添加定时任务或者轮询任务

邮件输出结果:

时间: 2024-09-15 13:44:06

自动监控MySQL表结构变更脚本的相关文章

mysql-schema-sync v0.2 mysql表结构自动同步工具

mysql-schema-sync 是一款使用go开发的.跨平台的.绿色无依赖的 MySQL 表结构自动同步工具. 用于解决多个环境数据库表结构不同步问题. 支持功能: 同步 新表 同步 字段 变动:新增.修改.删除 同步 索引 变动:新增.修改.删除 支持 预览(只对比不同步变动) 邮件通知变动结果 支持屏蔽更新 表.字段.索引 支持本地比线上额外多一些表.字段.索引 文章转载自 开源中国社区[https://www.oschina.net]

MySQL表结构的同步

现在全身心投入到MySQL中. 项目要求:将开发环境中的数据库的修改同步至线上环境. 开发者给出的解决办法是:利用像Python 中的South框架,自动将开发环境的变更同步至生产环境.这个对于DBA来说是无法承受的(除非是创建数据库结果类似的语句).数据库变更在生产环境执行,必须事先经过评估.对业务的影响降到最低,这个就设计到了重新设计索引,或者采用在线修改工具之类的.本文暂时先不讨论,下面给出表同步的解决办法. 以前的做法是通过navicate的工具将一个一个的数据库进行对比或者自己写脚本M

把MySQL表结构映射为Python中的对象的教程_python

ORM mysql的表结构是二维表,用python的数据结构表示出来就是一个列表,每一个记录是一个tuple.如下所示: [('1', ''huangyi),('2', ''letian),('3', 'xiaosi')] 这一行并不便于看出表的结构,可以把它换成对象的形式. class User(object): def __init__(self, id, name): self.id = id self.name = name 得到:   [ User('1', 'huangyi'), U

查看mysql表结构和表创建语句的方法(转)

查看mysql表结构的方法有三种:1.desc tablename;例如:要查看jos_modules表结构的命令:desc jos_modules;查看结果:mysql> desc jos_modules; +------------------+---------------------+------+-----+---------------------+----------------+ | Field | Type | Null | Key | Default | Extra | +-

MySQL表结构使用timestamp以自动获取当前时间

一.怀念ACCESS的时代 ACCESS的表结构如果想自动插入当前时间的时候,可以在列定义的时候输入默认值now ()就可以实现 自动插入当前时间了.而MySQL则行不通,MySQL这个东东默认是命令行下面的产物,如果不是后来有了 phpMyAdmin这个图形化界面东东的话,估计一般人都郁闷了.当然,后来MySQL官方也出了对应的gui工具 ,当然是exe的了.受到MySQL这个网络服务程序的好多使用者都是租用虚拟主机的前提下,貌似这种东东 又是个奢侈品.hoho,又跑题了,今天的话题是使用在M

请问net下怎么查看mysql表结构呢

问题描述 没有可视化工具,不能远程登录服务器.可以ftp,有数据库密码,网站页面可以连上数据库操作,怎么能导出表结构呢? 解决方案 解决方案二:ftp肯定不行,起码要有powershell.解决方案三:网页上可以执行sql语句这个有没有办法解决方案四:自己写上4.5行代码,就行了.你从网页上一个文本框输入sql语句,然后把查询出来的DataTable直接绑定给GridView,例如只需要写一句:this.gridView1.DataSource=ExecuteMySql(sql); 解决方案五:

输出MySql数据库表结构的PHP页面代码

经常为了方便和直观,我们会首先直接在数据库中设计出表,但是接下来又要将表的结构和设计编写在设计文档中,以便编码的时候可以直观的查询,一旦数据库表非常多,字段非常多的时候,这无疑是件非常郁闷的工作. 这是一个漂亮的PHP页面,可以自动输出MySql数据库所有表结构,大大方便了文档的编写工作,也同时非常方便编码的时候进行查询.当然在设计MySql数据库表和字段的时候详细填写表和字段的备注,这是非常好的习惯,对这个网页的输出也是最佳效果的. <!DOCTYPE html PUBLIC "-//W

mysql修改表结构方法实例详解_Mysql

本文实例讲述了mysql修改表结构方法.分享给大家供大家参考.具体如下: mysql修改表结构使用ALTER TABLE语句,下面就为您详细介绍mysql修改表结构的语句写法,希望对您学习mysql修改表结构方面能有所帮助. ALTER [IGNORE] TABLE tbl_name alter_spec [, alter_spec ...] alter_specification: ADD [COLUMN] create_definition [FIRST | AFTER column_nam

mysql 复制表数据,表结构的3种方法

 什么时候我们会用到复制表?例如:我现在对一张表进行操作,但是怕误删数据,所以在同一个数据库中建一个表结构一样,表数据也一样的表,以作备份.如果用mysqldump比较麻烦,备份.MYD,.MYI这样的文件呢,操作起来也还是麻烦. 一,复制表结构 方法1: mysql> create table a like users; //复制表结构 Query OK, 0 rows affected (0.50 sec)   mysql> show tables; +------+ | Tables_i