在之前的MySQL运维中,要求禁用触发器/存储过程/外键等一些数据库常见功能,因此对MySQL外键也相对比较陌生,今天特地探究下。

现有表TB001和TB002各包含6291456行数据,创建脚本如下:

CREATE TABLE `tb001` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`C1` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6684558 DEFAULT CHARSET=utf8; CREATE TABLE `tb002` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`C1` int(11) DEFAULT NULL,
`C2` int(11) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6356897 DEFAULT CHARSET=utf8;

现在在TB002上的C1列增加一个外键指向TB001的主键,有如下三种创建方式:
方式1:直接创建外键

查询:ALTER TABLE TB002 ADD CONSTRAINT `FK_C1` FOREIGN KEY (`C1`) REFERENCES `TB001` (`ID`)

共 6291456 行受到影响
执行耗时 : 55.889 sec
传送时间 : 1.003 sec
总耗时 : 56.892 sec

方式2:创建索引+创建外键

查询:alter table TB002 ADD INDEX FK_C1(C1)

共 0 行受到影响
执行耗时 : 9.676 sec
传送时间 : 1.001 sec
总耗时 : 10.678 sec
-------------------------------------------------- 查询:ALTER TABLE TB002 ADD CONSTRAINT `FK_C1` FOREIGN KEY (`C1`) REFERENCES `TB001` (`ID`) 共 6291458 行受到影响
执行耗时 : 1 min 9 sec
传送时间 : 1.001 sec
总耗时 : 1 min 10 sec

方式3:创建索引+创建外键+FOREIGN_KEY_CHECKS

查询:alter table TB002 ADD INDEX FK_C1(C1)

共 0 行受到影响

执行耗时   : 9.393 sec
传送时间 : 1.002 sec
总耗时 : 10.396 sec
-------------------------------------------------- 查询:SET @@foreign_key_checks=0 共 0 行受到影响 执行耗时 : 0 sec
传送时间 : 0 sec
总耗时 : 0 sec
-------------------------------------------------- 查询:ALTER TABLE TB002 ADD CONSTRAINT `FK_C1` FOREIGN KEY (`C1`) REFERENCES `TB001` (`ID`) 共 0 行受到影响 执行耗时 : 0.002 sec
传送时间 : 0.001 sec
总耗时 : 0.004 sec
-------------------------------------------------- 查询:SET @@foreign_key_checks=1 共 0 行受到影响
执行耗时 : 0 sec
传送时间 : 0 sec
总耗时 : 0 sec

如未设置FOREIGN_KEY_CHECKS=0,在创建外键过程中,会阻塞其他会话对表TB002的增删改操作(删除操作也不允许)。

从上面三种方式执行结果可以发现,创建外键最快的方式是:创建索引+创建外键+FOREIGN_KEY_CHECKS,在设置FOREIGN_KEY_CHECKS=0时,创建外键不会检查当前表中数据,属于元数据操作,因此很快完成,对其他会话影响较小,但无法保证表中数据满足外键约束。

当使用pt-online-schema-change修改被外键依赖的表时,如果参数alter-foreign-keys-method指定为drop_swap,那么在日志中可以看到rename被依赖表名前,会使用FOREIGN_KEY_CHECKS来屏蔽检查:

2019-10-10T07:48:06.692791-05:00     3428 Query    SHOW WARNINGS
2019-10-10T07:48:06.693796-05:00 3428 Query SHOW GLOBAL STATUS LIKE 'Threads_running'
2019-10-10T07:48:06.695144-05:00 3428 Query ANALYZE TABLE `dadaabc_test`.`_tb001_new` /* pt-online-schema-change */
2019-10-10T07:48:06.706498-05:00 3428 Query SET foreign_key_checks=0
2019-10-10T07:48:06.707180-05:00 3428 Query DROP TABLE IF EXISTS `dadaabc_test`.`tb001`
2019-10-10T07:48:06.731187-05:00 3428 Query RENAME TABLE `dadaabc_test`.`_tb001_new` TO `dadaabc_test`.`tb001`
2019-10-10T07:48:06.734388-05:00 3428 Query DROP TRIGGER IF EXISTS `dadaabc_test`.`pt_osc_dadaabc_test_tb001_del`
2019-10-10T07:48:06.736416-05:00 3428 Query DROP TRIGGER IF EXISTS `dadaabc_test`.`pt_osc_dadaabc_test_tb001_upd`
2019-10-10T07:48:06.736893-05:00 3428 Query DROP TRIGGER IF EXISTS `dadaabc_test`.`pt_osc_dadaabc_test_tb001_ins`
2019-10-10T07:48:06.737513-05:00 3428 Query SHOW TABLES FROM `dadaabc_test` LIKE '\_tb001\_new'

drop_swap会删除掉原表,再将新表改名,如果在删除原表之后重命名新表完成之前出现故障,会导致应用长时间异常。

FOREIGN_KEY_CHECKS相关

FOREIGN_KEY_CHECKS在官方文档解释:

If set to 1 (the default), foreign key constraints for InnoDB tables are checked. If set to 0, foreign key constraints are ignored, with a couple of exceptions. When re-creating a table that was dropped, an error is returned if the table definition does not conform to the foreign key constraints referencing the table. Likewise, an ALTER TABLE operation returns an error if a foreign key definition is incorrectly formed. 

Setting foreign_key_checks to 0 also affects data definition statements: DROP SCHEMA drops a schema even if it contains tables that have foreign keys that are referred to by tables outside the schema, and DROP TABLE drops tables that have foreign keys that are referred to by other tables. 

Setting foreign_key_checks to 1 does not trigger a scan of the existing table data. Therefore, rows added to the table while foreign_key_checks=0 will not be verified for consistency. 

当FOREIGN_KEY_CHECKS设置为0时,外键约束会被忽略,而当FOREIGN_KEY_CHECKS设置为1时不会触发约束检查。

删除外键

## 删除外键,但外键依赖的索引不会被删除
ALTER TABLE TB002 DROP FOREIGN KEY FK_C1; ## 删除外键依赖的索引
ALTER TABLE TB002 DROP INDEX FK_C1;

导出实例下的外键

# coding: utf-8
import pymysql
import os, traceback, datetime pymysql.install_as_MySQLdb()
import MySQLdb working_folder = os.path.dirname(os.path.abspath(__file__))
default_log_file = os.path.join(working_folder, "default_log.txt")
error_log_file = os.path.join(working_folder, "error_log.txt")
create_key_file = os.path.join(working_folder, "create_key_script.txt")
drop_key_file = os.path.join(working_folder, "drop_key_script.txt") class MySQLForeignKeyLoader(object):
def __init__(self, mysql_host, mysql_port, mysql_user, mysql_password,
mysql_charset, database_name, connect_timeout=60):
self.mysql_host = mysql_host
self.mysql_port = mysql_port
self.mysql_user = mysql_user
self.mysql_password = mysql_password
self.mysql_charset = mysql_charset
self.database_name = database_name
self.connect_timeout = connect_timeout def highlight(self, message):
return "%s[30;2m%s%s[1m" % (chr(27), message, chr(27)) def print_warning_message(self, message):
"""
以红色字体显示消息内容
:param message: 消息内容
:return: 无返回值
"""
message = str(message)
print(self.highlight('') + "%s[31;1m%s%s[0m" % (chr(27), message, chr(27)))
self.write_file(error_log_file, message) def print_info_message(self, message):
"""
以绿色字体输出提醒性的消息
:param message: 消息内容
:return: 无返回值
"""
message = str(message)
print(self.highlight('') + "%s[32;2m%s%s[0m" % (chr(27), message, chr(27)))
self.write_file(default_log_file, message) def write_file(self, 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(10))
file_handle.flush()
file_handle.close() def get_mysql_connection(self, is_use_dict=False):
"""
根据默认配置返回数据库连接
:return: 数据库连接
"""
if is_use_dict:
conn = MySQLdb.connect(
host=self.mysql_host,
port=self.mysql_port,
user=self.mysql_user,
passwd=self.mysql_password,
connect_timeout=self.connect_timeout,
charset=self.mysql_charset,
db=self.database_name,
cursorclass=pymysql.cursors.DictCursor
)
else:
conn = MySQLdb.connect(
host=self.mysql_host,
port=self.mysql_port,
user=self.mysql_user,
passwd=self.mysql_password,
connect_timeout=self.connect_timeout,
charset=self.mysql_charset,
db=self.database_name,
cursorclass=pymysql.cursors.Cursor
)
return conn def mysql_query(self, sql_script, sql_paras=None, is_use_dict=True):
"""
执行SQL
:param sql_script:
:param sql_paras:
:param is_use_dict:
:return:
"""
cursor = None
mysql_conn = None
try:
mysql_conn = self.get_mysql_connection(is_use_dict=is_use_dict)
cursor = mysql_conn.cursor()
if sql_paras is not None:
cursor.execute(sql_script, sql_paras)
else:
cursor.execute(sql_script)
exec_result = cursor.fetchall()
return exec_result
except Exception as ex:
warning_message = """
mysql query exception,
sql script:{sql_script}
sql paras:{sql_paras}
exception:{sql_exception}
trace back:{sql_trace}
""".format(
sql_script=sql_script,
sql_paras=str(sql_paras),
sql_exception=str(ex),
sql_trace=str(traceback.format_exc())
)
self.print_warning_message(warning_message)
raise Exception(str(ex))
finally:
if cursor is not None:
cursor.close()
if mysql_conn is not None:
mysql_conn.close() def mysql_exec(self, sql_script, sql_paras=None):
"""
执行SQL
:param sql_script:
:param sql_paras:
:return:
"""
cursor = None
mysql_conn = None
try:
mysql_conn = self.get_mysql_connection(is_use_dict=False)
cursor = mysql_conn.cursor()
if sql_paras is not None:
cursor.execute(sql_script, sql_paras)
else:
cursor.execute(sql_script)
affect_rows = cursor.rowcount
mysql_conn.commit()
return affect_rows
except Exception as ex:
warning_message = """
mysql query exception,
sql script:{sql_script}
sql paras:{sql_paras}
exception:{sql_exception}
trace back:{sql_trace}
""".format(
sql_script=sql_script,
sql_paras=str(sql_paras),
sql_exception=str(ex),
sql_trace=str(traceback.format_exc())
)
self.print_warning_message(warning_message)
raise Exception(str(ex))
finally:
if cursor is not None:
cursor.close()
if mysql_conn is not None:
mysql_conn.close() def get_foreign_keys(self):
sql_script = """
SELECT *
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA NOT IN('sys','mysql','information_schema','proformance_schema')
"""
return self.mysql_query(sql_script=sql_script, is_use_dict=True) def get_drop_foreign_key_script(self, source_database_name, source_table_name, foreign_key_name):
sql_script = "ALTER TABLE `{database_name}`.`{table_name}` DROP FOREIGN KEY `{foreign_key_name}`;".format(
database_name=source_database_name,
table_name=source_table_name,
foreign_key_name=foreign_key_name
)
self.print_info_message(sql_script)
return sql_script def get_create_table_script(self, source_database_name, source_table_name):
sql_script = "SHOW CREATE TABLE `{database_name}`.`{table_name}`;".format(
database_name=source_database_name,
table_name=source_table_name
)
query_result = self.mysql_query(sql_script=sql_script, is_use_dict=False)
if len(query_result) > 0:
return query_result[0][1]
else:
return "" def get_create_foreign_key_script(self, source_database_name, source_table_name, foreign_key_name):
table_script = self.get_create_table_script(
source_database_name=source_database_name,
source_table_name=source_table_name
)
table_script = table_script.encode("utf-8")
script_items = table_script.splitlines()
constraint_script = ""
for script_item in script_items:
if script_item.lower().find(str(foreign_key_name).lower()) > 0 \
and script_item.lower().find(" FOREIGN KEY ".lower()) > 0 \
and script_item.lower().find(" CONSTRAINT ".lower()) > 0 \
and script_item.lower().find(" REFERENCES ".lower()) > 0:
constraint_script = script_item
break
if constraint_script <> "":
foreign_key_script = "ALTER TABLE `{database_name}`.`{table_name}` ADD {constraint_script};".format(
database_name=source_database_name,
table_name=source_table_name,
constraint_script=constraint_script
)
else:
foreign_key_script = "## Can not load script for foreign key `{foreign_key_name}` in `{database_name}`.`{table_name}`;".format(
database_name=source_database_name,
table_name=source_table_name,
foreign_key_name=foreign_key_name
)
self.print_info_message(foreign_key_script)
return foreign_key_script def load_foreign_key_scripts(self):
self.print_info_message("start to load script for foreign keys")
foreign_key_list = self.get_foreign_keys()
for foreign_key_item in foreign_key_list:
source_database_name = foreign_key_item["CONSTRAINT_SCHEMA"]
source_table_name = foreign_key_item["TABLE_NAME"]
foreign_key_name = foreign_key_item["CONSTRAINT_NAME"]
self.print_info_message(
"load script for foreign key `{foreign_key_name}` in `{source_database_name}`.`{source_table_name}`".format(
source_database_name=source_database_name,
source_table_name=source_table_name,
foreign_key_name=foreign_key_name
))
drop_script = self.get_drop_foreign_key_script(
source_database_name=source_database_name,
source_table_name=source_table_name,
foreign_key_name=foreign_key_name
)
self.write_file(drop_key_file, drop_script)
create_script = self.get_create_foreign_key_script(
source_database_name=source_database_name,
source_table_name=source_table_name,
foreign_key_name=foreign_key_name
)
self.write_file(create_key_file, create_script)
self.print_info_message("end to load script for foreign keys") def main():
loader = MySQLForeignKeyLoader(
mysql_host="192.168.0.1",
mysql_port=3306,
mysql_user="root",
mysql_password="root",
mysql_charset="utf8",
database_name="mysql"
)
loader.load_foreign_key_scripts() if __name__ == '__main__':
main()

MySQL Table--MySQL外键的更多相关文章

  1. [原创]MYSQL中利用外键实现级联删除和更新

    MySQL中利用外键实现级联删除.更新 MySQL支持外键的存储引擎只有InnoDB,在创建外键的时候,要求父表必须有对应的索引,子表在创建外键的时候也会自动创建对应的索引.在创建索引的时候,可以指定 ...

  2. mysql之创建外键报150错误的处理方法

    这几天由于在赶项目进度,也就没有及时记录下自己的学习情况 ,在完成项目的这段时间里,碰到了很多问题,在解决问题的过程中学习了不少技巧. 这里就主要介绍一下在mysql数据库中为表之间建立外键时报100 ...

  3. MySQL数据库建立外键失败的原因总结

    在MySQL数据库创建外键时,经常会发生一些错误,这是一件很令人头疼的事.一个典型的错误就是:Can’t create table... 的错误.在很多实例中,这种错误的发生都是因为mysql一直以来 ...

  4. MySQL里创建外键时错误的解决

    --MySQL里创建外键时错误的解决 --------------------------------2014/04/30 在MySQL里创建外键时(Alter table xxx add const ...

  5. mysql foreign key(外键) 说明与实例

    一,什么是foreign key,及其完整性 个人觉得,foreign key就是表与表之间的某种约定的关系,由于这种关系的存在,我们能够让表与表之间的数据,更加的完整,关连性更强.关于完整性,关连性 ...

  6. 1、Mysql无法创建外键的原因 2、MySql 外键约束 之CASCADE、SET NULL、RESTRICT、NO ACTION分析和作用

    在Mysql中创建外键时,经常会遇到问题而失败,这是因为Mysql中还有很多细节需要我们去留意,我自己总结并查阅资料后列出了以下几种常见原因. 1.  两个字段的类型或者大小不严格匹配.例如,如果一个 ...

  7. MySQL 中的外键

    表和表之间可存在引用关系,这在抽象数据到表时,是很常见的.这种联系是通过在表中创建外键(foreign key)来实现的. 比如一个订单,可能关联用户表和产品表,以此来记录谁买了什么产品. 约定两个概 ...

  8. MySQL truncate含有外键约束的条目报错

    1.报错信息: Cannot truncate a table referenced in a foreign key constraint 2.出现错误操作: truncate table a1; ...

  9. Mysql无法创建外键的原因

    在Mysql中创建外键时,经常会遇到问题而失败,这是因为Mysql中还有很多细节需要我们去留意,我自己总结并查阅资料后列出了以下几种常见原因. 1.  两个字段的类型或者大小不严格匹配.例如,如果一个 ...

  10. 转!!!Mysql无法创建外键的原因

    在Mysql中创建外键时,经常会遇到问题而失败,这是因为Mysql中还有很多细节需要我们去留意,我自己总结并查阅资料后列出了以下几种常见原因. 1.  两个字段的类型或者大小不严格匹配.例如,如果一个 ...

随机推荐

  1. qt中窗体全屏

    原文地址:https://www.cnblogs.com/wiessharling/p/3750461.html 近期在学习QT时遇到了很多问题这也是其中一个,个人通过在各种书籍和网络上的查阅找到了一 ...

  2. C++ CGI报“资源访问错误”问题分析

    一线上CGI偶发性会报“资源访问错误”,经过分析得出是因为CgiHost没有读取到CGI的任务输出,即CGI运行完成后连HTTP头都没有一点输出. 然而实际上,不可能没有任何输出,因为CGI至少有无条 ...

  3. pyhton2 and python3 生成随机数字、字母、符号字典(用于撞库测试/验证码等)

    本文介绍Python3中String模块ascii_letters和digits方法,其中ascii_letters是生成所有字母,从a-z和A-Z,digits是生成所有数字0-9.string.p ...

  4. p1842 奶牛玩杂技 题解

    感觉其他dalao讲的不是很明白啊,我这样的蒟蒻看不懂啊. 在luogu这个dalao遍地的地方我蒟蒻看个题解也不明白,我为跟我同病相怜的蒟蒻写一篇吧 其实真是不太明白,大部分题解都是只说 体重大的在 ...

  5. 记一次Pr中视频蜜汁卡顿往复和解决方法

    目录 问题 换素材的起因 灵异素材 无端联想 解决 问题 换素材的起因 本来视频剪了一晚剪完了,导出一看,好家伙,糊到上世纪.原来素材的像素大小都没法看,这视频素材我是从别人U盘拷过来的,可他竟然是用 ...

  6. Salesforce LWC学习(八) Look Up组件实现

    本篇参考https://www.salesforcelwc.in/2019/10/lookup-in-lwc.html,感谢前人种树. 我们做lightning的时候经常会遇到Look up 或者MD ...

  7. IIS 安装 .net core 绑定为 https 使用SSL证书

    前提条件: 自己服务器(Windows Server 2016)运行 dotnet .\Web****.dll 服务是可以使用http访问的 但由于实际情况必须使用https 思想历程,但未用: 1. ...

  8. oracle 之 using 使用

    oracle  中 using关键字使用规则: 1.查询必须是等值连接.2.等值连接中的列必须具有相同的名称和数据类型. 使用using关键字简化连接时,需要注意以下几点:1.使用 table1表和 ...

  9. centos上nginx转发tcp请求

    下载最新版nginx > wget http://nginx.org/download/nginx-1.17.1.tar.gz 解压缩 > tar zxvf nginx-1.17.1.ta ...

  10. 模拟 + 暴搜 --- Help Me with the Game

    Help Me with the Game Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 3175   Accepted: ...