MySQL 核心内幕:从索引原理、字段选型到日志机制与外键约束,一篇打通数据库任督二脉

B+Tree 索引

不管是初识Mysql,还是聊到Mysql的优化,或者是面试中常用到的问题,Mysql的索引能占比非常大,了解和掌握Mysql索引是运用Mysql的关键点,Expain工具分析的画外音是这句执行的Sql语句是否用上了索引

B+树索引就是基于B+树发展而来的,通常在InnoDB上对某个字段添加索引,就是对这个字段构建一棵 B+树。当查询条件是该索引字段时,查询速度非常快,对比逐行扫描,效率明显高很多。

MySQL 的索引,按具体作用划分,常用的为聚集索引(有的资料里也叫聚簇索引)、辅助索引、唯一索引和联合索引,先看下面的表结构,在阅读内容。

sql 复制代码
CREATE TABLE `sys_admin` (
  `admin_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `admin_name` varchar(64) NOT NULL DEFAULT '' COMMENT '真实姓名',
  `department_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '所属部门ID(匹配部门表)',
  `role_id` int(11) NOT NULL COMMENT '当前角色(修正为int(11),匹配角色表)',
  PRIMARY KEY (`admin_id`) USING BTREE,
  UNIQUE KEY `uk_admin_account` (`admin_account`) COMMENT '管理员账号(手机号)唯一',
  KEY `fk_sys_admin_department_id` (`department_id`),
  KEY `fk_sys_admin_role_id` (`role_id`),
  CONSTRAINT `fk_sys_admin_department_id` FOREIGN KEY (`department_id`) REFERENCES 
  `sys_department` (`department_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_sys_admin_role_id` FOREIGN KEY (`role_id`) REFERENCES
  `sys_role` (`role_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='后台管理员表';

聚集索引

平时在数据表中定义的主键,就是聚集索引,聚集索引的存储的是整行数据都放在B+Tree的叶子节点,使用主键查询速度快是因为在查找到当前的叶子节点上就查找到了数据,少了回表的步骤,所以快到了这里。 InnoDB通过主键聚集数据,如果没有定义主键,那么InnoDB会选择第一个非空的唯一索引代替,如果没有非空的唯一索引,那么InnoDB会隐式定义一个ROW ID代替。

可以简单的理解为数据表里定义的主键,就是聚集索引,在sys_admin表中的admin_id定义的主键就是聚集索引。

辅助索引

辅助索引,也称为二级索引,单张表可以有多个。在使用二级索引时,因为它只存储了索引字段的值和主键,所以如果需要查询其他列的数据,就需要先通过二级索引中的值找到对应的主键,再通过主键找到聚簇索引中其他列的数据。这个过程称为回表

department_idrole_id就是普通索引,需要注意的是在 MySQL 中,BTreeHash 是两种最常见的索引方法,它们的底层数据结构和适用场景有明显区别。

  • BTree 索引基于平衡多叉树(B-Tree 或 B+Tree)的数据结构,支持范围查询 (如 >, <, BETWEEN, LIKE 'prefix%'),排序ORDER BY)和分组GROUP BY)操作,对前缀匹配的模糊查询友好。
  • Hash 索引基于哈希表的数据结构,仅支持精确等值查询=<=>),不支持范围查询。

问题1:如果我的数据表类型是varchar 或者是 text 我可以选择BTree 么?

varchar 是 MySQL 中最常用的字符串类型(如用户名、手机号、邮箱、地址等),BTree 索引对这类字段的适配性非常好。

sql 复制代码
-- 创建表(varchar 字段)
CREATE TABLE user_info (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 为 varchar 字段创建 BTree 索引(默认就是 BTree,可省略 USING BTREE)
CREATE INDEX idx_username ON user_info(username) USING BTREE;
CREATE INDEX idx_email ON user_info(email); -- 省略 USING BTREE 效果一致

text 是大文本类型(如文章内容、备注等),MySQL 不允许直接为完整的 text 字段创建 BTree 索引(因为字段长度可能远超索引页大小),但可以指定前 N 个字符创建前缀索引。

sql 复制代码
-- 创建表(text 字段)
CREATE TABLE article (
    id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    content TEXT NOT NULL -- 大文本字段
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 错误写法:直接为 text 字段创建索引(会报错)
-- CREATE INDEX idx_content ON article(content);

-- 正确写法:指定前缀长度(取前 100 个字符创建 BTree 索引)
CREATE INDEX idx_content_prefix ON article(content(100)) USING BTREE;

一句话总结:几乎不需要考虑 Hash 索引,BTree 是唯一实用的选择。

唯一索引

唯一索引由唯一约束和二级索引两部分组成)。为某个字段添加唯一索引之后,那么写入该字段的值必须是不同的,Mysql会有检测,重复输入会报错。

联合索引

联合索引适用于where条件中的多列组合,并且在某些场景中可以避免回表。

联合索引也是按照键值排序的。需要注意的是,当a字段和b字段都作为条件时,查询是可以使用索引的;单独对a字段进行查询也是可以使用索引的。但是单独对b字段进行查询就无法使用索引,因为在图2-8中的叶子节点上,b字段对应的值为c、b、a、e、i、g、f,显然是无序的,所以无法使用b字段的索引。

定义字段类型

定义字段类型原则:确保数据不会超过取值范围,在这个前提之下,再去考虑 如何节省存储空间。 在实际工作中,系统故障产生的成本远远超过增加几个字段存储空间所产 生的成本。

整数类型

整数类型一共有 5 种,包括 TINYINT、SMALLINT、MEDIUMINT、INT (INTEGER) 和 BIGINT,它们的区别如下表所示:

类型 有符号数取值范围 无符号数取值范围 占用字节数 适用场景
TINYINT -128 ~ 127 0 ~ 255 1 一般用于枚举数据,比如系统设定等取值范围很小且固定的场景
SMALLINT -32768 ~ 32767 0 ~ 65535 2 可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等
MEDIUMINT -8388608 ~ 8388607 0 ~ 16777215 3 用于较大整数的计算,比如车站每日的客流量等
INT (INTEGER) -2147483648 ~ 2147483647 0 ~ 4294967295 4 取值范围足够大,一般情况下不用考虑超限问题,用得最多
BIGINT -9223372036854775808 ~ 9223372036854775807 0 ~ 18446744073709551615 8 只有当你处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、证券公司衍生产品持仓等

浮点数类型和定点数类型

FLOAT 表示单精度浮点数; DOUBLE 表示双精度浮点数;这两个类型都是不可信的,有个致命缺陷,就是不精准。 因此,在一些对精确度要求 较高的项目中,千万不要使用浮点数,不然会导致结果错误,甚至是造成不可挽回的损失。那么,为什么会存在这样的误差呢? 问题还是出在 MySQL 对浮点类型数据的存储方式 上。

MySQL 用 4 个字节存储 FLOAT 类型数据,用 8 个字节来存储 DOUBLE 类型数据。无论 哪个,都是采用二进制的方式来进行存储的。比如 9.625,用二进制来表达,就是 1001.101,或者表达成 1.001101×2^3。看到了吗?如果尾数不是 0 或 5(比如 9.624),你就无法用一个二进制数来精确表达。怎么办呢?就只好在取值允许的范围内进 行近似(四舍五入)。

MySQL 精准的数据类型要使用 DECIMAL,它是把十进制数的整数 部分和小数部分拆开,分别转换成十六进制数,进行存储。这样,所有的数值,就都可以 精准表达了,不会存在因为无法表达而损失精度的问题。

下面是创建包含 DECIMAL 字段的表的示例,直观展示不同定义的效果:

sql 复制代码
-- 创建测试表,包含不同DECIMAL类型的字段
CREATE TABLE test_decimal (
    id INT PRIMARY KEY AUTO_INCREMENT,
    small_money DECIMAL(5,2),  -- 小额金额:-999.99 ~ 999.99
    big_money DECIMAL(10,2),   -- 大额金额:-99999999.99 ~ 99999999.99
    integer_num DECIMAL(6,0)   -- 整数:-999999 ~ 999999
);

-- 插入合法数据
INSERT INTO test_decimal (small_money, big_money, integer_num)
VALUES (123.45, 12345678.90, 999999);

-- 插入超出范围的数据(会报错)
-- 错误:small_money 超出 DECIMAL(5,2) 范围
INSERT INTO test_decimal (small_money) VALUES (1000.00);

文本类型

MySQL 还支持 CHAR、 VARCHAR、TEXT这三个就好,使用的场景如下:

优先用 CHAR:当字符串长度固定且 ≤ 255 字符时,比如手机号、性别、邮编、状态码等,CHAR 效率最高,且避免空格问题(注意查询时空格被去除的特性)。

优先用 VARCHAR:当字符串长度不固定、中等长度(1 - 65535 字符)且需要默认值 / 全字段索引时,比如用户名、标题、地址等,VARCHAR 空间利用率更高。

优先用 TEXT:当字符串长度超过 VARCHAR 上限,或不需要默认值、允许略低的查询效率时,比如文章内容、日志、富文本等,TEXT 是唯一选择。

类型 最大长度(字符,utf8 下) 存储额外开销
TINYTEXT 255 1 字节
TEXT 65535 2 字节
MEDIUMTEXT 16777215 3 字节
LONGTEXT 4294967295 4 字节

日期与时间类型

日期与时间是重要的信息,在我们的系统中,几乎所有的数据表都用得到。原因是客户需 要知道数据的时间标签,从而进行数据查询、统计和处理。

类型 日期格式 范围 占用字节数
YEAR YYYY 1901 ~ 2155 1
TIME HH:MM:SS -838:59:59 ~ 838:59:59 3
DATE YYYY-MM-DD 1000-01-01 ~ 9999-12-3 3
DATETIME YYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 8
TIMESTAMP YYYY-MM-DD HH:MM:SS 1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC 4

用得最多的日期时间类型,就是 DATETIME。在实际项目 中,尽量用 DATETIME 类型。其实在之前的实践中有一个误区,一直使用时间戳作为存储时间类型,这是不专业的,绝大多数业务优先选 DATETIME :可读性高、操作便捷、范围广,是 MySQL 设计的原生时间类型,符合最佳实践;MySQL 8.0 推荐用 DATETIME(6):支持微秒级精度,满足高并发场景,说明官方已经像这个方法靠拢了。

日志 Log

Binlog包含描述数据库修改的语句,如create table、update等数据变更语句,不会记录类似 select、show 等不修改数据的语句。

Binlog 开启/关闭

如果要开启Binlog,就需要在配置文件的[mysqld]中加上如下语句:

bash 复制代码
[mysqld]
# 开启 binlog(核心配置)
log_bin = ON
# binlog 日志文件的存储路径和前缀(可选,默认在数据目录)
log_bin = /var/lib/mysql/mysql-bin
# 服务器ID(开启 binlog 必须配置,主从复制也依赖)
server_id = 1
# binlog 格式(可选,常用 ROW/STATEMENT/MIXED)
binlog_format = ROW

binlog 文件

bash 复制代码
# binlog 文件
[root@localhost var]# ll | grep mysql-bin
-rw-r-----  1 mysql mysql   276631 Mar  5 17:35 mysql-bin.000087
-rw-r-----  1 mysql mysql   886260 Mar  9 10:42 mysql-bin.000088
-rw-r-----  1 mysql mysql       38 Mar  5 17:35 mysql-bin.index

关闭binlog日志

bash 复制代码
[mysqld]
# 方式1:注释掉 log_bin 相关配置(推荐,方便后续恢复)
# log_bin = ON
# log_bin = /var/lib/mysql/mysql-bin  # 若配置了日志路径,也注释掉

# 方式2:直接设置 log_bin = OFF(部分版本兼容)
log_bin = OFF

Binlog的落盘

Binlog同步到磁盘的频率由sync_binlog参数控制。sync_binlog参数大致有以下几种配置。

● sync_binlog=0,禁用MySQL服务将Binlog同步到磁盘的功能,是由操作系统控制Binlog 的刷盘。在这种情况下,性能比较好,但是当操作系统崩溃时可能会丢失部分事务。

● sync_binlog=1,每个事务都会同步到磁盘。这是最安全的设置,但是磁盘写入次数的增加可能会导致性能下降。

● sync_binlog=N,表示每N个事务Binlog同步一次到磁盘。当操作系统崩溃时,服务器提交的事务可能没有被刷新到 Binlog 中,此时可能会丢失部分事务,虽然设置比较大的值可以提高性能,但是数据丢失的风险也会增加。

General Log

General Log 会记录 MySQL 接收到的所有 SQL 请求 (包括查询、修改、连接断开等),主要用于调试和审计,但会产生大量日志文件,生产环境不建议长期开启

bash 复制代码
[mysqld]
# 核心:开启 General Log(默认 OFF)
general_log = ON

# 可选:日志输出方式(FILE/TABLE/BOTH,默认 FILE)
log_output = FILE

# 可选:指定日志文件路径和名称(绝对路径)
general_log_file = /var/lib/mysql/mysql_general.log

Slow Log

Slow Log 可以用于查找执行时间比较长的查询。当优化数据库时,Slow Log往往是需要重点关注的日志文件。

Error Log

MySQL的Error Log不仅包含错误信息,还包含启动和关闭的一些记录。在很多情况下,定位问题第一时间应该查看Error Log。

bash 复制代码
[mysqld]
# ====================== 1. 通用查询日志(General Log) ======================
# 核心开关:开启/关闭通用日志(记录所有SQL请求,生产环境默认关闭)
general_log = OFF
# 日志输出方式:FILE(文件)/TABLE(mysql.general_log表)/BOTH(同时输出)
log_output = FILE
# 通用日志文件路径(绝对路径,确保mysql用户有写入权限)
general_log_file = /var/lib/mysql/mysql_general.log

# ====================== 2. 二进制日志(Binlog) ======================
# 核心开关:开启/关闭二进制日志(主从复制、数据恢复依赖,生产环境建议开启)
log_bin = ON
# 二进制日志文件前缀(路径+前缀,默认在数据目录,文件名为 mysql-bin.000001 等)
log_bin = /var/lib/mysql/mysql-bin
# 服务器ID(开启binlog必须配置,主从复制时需唯一,取值1-2^32-1)
server_id = 1
# binlog格式:ROW(行级,推荐)/STATEMENT(语句级)/MIXED(混合)
binlog_format = ROW
# binlog过期清理时间(自动删除N天前的binlog,避免磁盘占满)
expire_logs_days = 7
# 单个binlog文件大小上限(达到后自动切换新文件,单位字节,默认1GB)
max_binlog_size = 1073741824

# ====================== 3. 慢查询日志(Slow Query Log) ======================
# 核心开关:开启/关闭慢查询日志(记录执行时间超过阈值的SQL)
slow_query_log = ON
# 慢查询日志文件路径
slow_query_log_file = /var/lib/mysql/mysql_slow.log
# 慢查询阈值(单位秒,默认10,建议设为1,记录执行超1秒的SQL)
long_query_time = 1
# 记录未使用索引的查询(即使执行时间未超过阈值,生产环境建议开启)
log_queries_not_using_indexes = ON
# 不记录管理类语句(如ALTER TABLE,减少日志冗余)
log_slow_admin_statements = OFF

# ====================== 4. 错误日志(Error Log) ======================
# 错误日志文件路径(记录MySQL启动/运行/关闭的错误信息,必须开启)
log_error = /var/lib/mysql/mysql_error.log
# 错误日志级别:ERROR/WARNING/INFO/DEBUG(生产环境建议ERROR/WARNING)
log_error_verbosity = 2

# ====================== 5. 其他日志配置 ======================
# 记录所有DDL语句(CREATE/DROP/ALTER等,默认开启)
log_bin_trust_function_creators = ON
# 禁止记录指定数据库的binlog(多个库用逗号分隔,按需配置)
# binlog_ignore_db = test,tmp
# 仅记录指定数据库的binlog(多个库用逗号分隔,按需配置)
# binlog_do_db = prod_db

Redo Log 、和 Undo Log

Redo Log 和 Undo Log是Mysql InnoDb引擎中所独有的,可以说它们是InnoDB 拥有事务这个功能的基石,也是InnoDB的灵魂所在。MySQL采用的是WAL(Write-Ahead Logging)技术,也就是先写日志再写磁盘。如果有修改操作,则先将操作记录在Redo Log Buffer中,然后将Redo Log Buffer中的数据刷新到磁盘的日志文件中,最后写入数据文件中。

Redo Log

Redo Log由两部分组成:Redo Log缓冲区和Redo Log文件。

Redo Log记录了这个页做了什么改动,对Redo Log进行落盘,不仅可以保证数据持久化,还可以提高写入效率。

  • Redo Log记录的是缓冲池中页的修改记录,因此每次只需要对Redo Log进行落盘,而不需要对整个页落盘。这大大降低了落盘的数据量,同时节约了IO成本,从而提高了写入效率。
  • Redo Log落盘,就可以将这些随机IO转换成顺序IO,这样也可以提高写入效率。

Redo Log另一个很重要的作用就是崩溃恢复能力。事务在提交时会把Redo Log刷新到磁盘中,如果此时系统宕机了,那么重启之后,只要根据Redo Log的记录把数据页未更新的部分更新一下,就可以恢复事务所做的数据变更。

Redo Log的落盘

Redo Log缓冲区的大小通过innodb_log_buffer_size参数来设置。当事务更新时,一半先写入 Redo Log 缓冲区,再写入 Redo Log 文件。而具体的写入频率由innodb_flush_log_at_trx_commit参数控制。

  • 0,每秒将日志缓冲区写入日志文件一次,并在日志文件上执行磁盘刷新操作,未刷新日志的事务可能会在崩溃中丢失,此时InnoDB不再符合事务持久性的要求。
  • 1,在每次提交事务时,日志缓冲区都会写入日志文件中,并在日志文件上执行磁盘刷新操作。
  • 2,在每次提交事务后写入日志,并且日志每秒刷新一次到磁盘。未刷新日志的事务可能会在崩溃中丢失。当MySQL服务发生宕机,但操作系统没有发生宕机时,不会出现数据丢失。但是当操作系统宕机时,重启后可能会丢失 Redo Log缓冲中还没有刷新到Redo Log文件中的数据。

Redo Log的数量及大小修改

在配置文件的[mysqld]中补充类似于如下的配置:配置3个大小为64M的Redo Log,文件名类似于ib_logfile0。Redo Log是循环写的,文件存储位置在/usr/local/mysql/var, innodb_log_file_size:控制Redo Log的大小,默认值为48 MB,但不能超过512GB除以innodb_log_files_in_group参数配置的值,如果innodb_log_files_in_group参数的值为2,那么innodb_log_file_size参数的最大值为256 GB。

bash 复制代码
innodb_log_group_home_dir = /usr/local/mysql/var
innodb_log_file_size = 64M
innodb_log_files_in_group = 3
# 文件存储如下
-rw-r-----. 1 mysql mysql 67108864 Mar  9 11:01 ib_logfile0
-rw-r-----. 1 mysql mysql 67108864 Mar  9 11:01 ib_logfile1
-rw-r-----. 1 mysql mysql 67108864 Mar  9 11:01 ib_logfile2

CheckPoint:

CheckPoint是当前要擦除的位置,而Redo Log在擦除记录前会确保记录已经更新到数据文件中。因此,也可以认为CheckPoint就是控制数据页刷新到磁盘的操作。

CheckPoint的作用就是将缓冲池中的数据页刷新到磁盘。如果发生宕机重启,那么已经刷新的数据页不需要再进行恢复,只需要恢复CheckPoint之后的操作。

外键

外键就是从表中用来引用主表中数据的那个公共字段,外键约束就是约束的一种,比如说表结构必须相同,监控对主表中数据的删除操作。如果发现要删除的主 表记录,正在被从表中某条记录的外键字段所引用,MySQL 就会提示错误,从而确保了关 联数据不会缺失。

一句话总结就是:Mysql以牺牲了性能的代价,尽可能的避免数据库的脏数据。

sql 复制代码
CREATE TABLE `sys_admin` (
  `admin_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `admin_name` varchar(64) NOT NULL DEFAULT '' COMMENT '真实姓名',
  `department_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '所属部门ID(匹配部门表)',
  `role_id` int(11) NOT NULL COMMENT '当前角色(修正为int(11),匹配角色表)',
  PRIMARY KEY (`admin_id`) USING BTREE,
  UNIQUE KEY `uk_admin_account` (`admin_account`) COMMENT '管理员账号(手机号)唯一',
  KEY `fk_sys_admin_department_id` (`department_id`),
  KEY `fk_sys_admin_role_id` (`role_id`),
  CONSTRAINT `fk_sys_admin_role_id` FOREIGN KEY (`role_id`) REFERENCES
  `sys_role` (`role_id`) ON UPDATE CASCADE,
  CONSTRAINT `fk_sys_department` FOREIGN KEY (`department_id`) REFERENCES
  `sys_department` (`department_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='后台管理员表';

新增外键约束:下面的语句的意思是 在sys_admin表中关联一个名称是fk_sys_department来自表sys_department.department_id的字段。

sql 复制代码
-- 新增外键约束
ALTER TABLE sys_admin
ADD CONSTRAINT fk_sys_department 
FOREIGN KEY (department_id) 
REFERENCES `sys_department` (`department_id`)
ON UPDATE CASCADE;

外键的约束规则(ON DELETE / ON UPDATE)

规则 说明
RESTRICT 拒绝操作(默认规则):父表数据被引用时,无法删除 / 修改
CASCADE 级联操作:父表删除 / 修改,子表对应的记录也跟着删除 / 修改
SET NULL 置空操作:父表删除 / 修改,子表外键字段设为 NULL(需外键字段允许 NULL)
NO ACTION 与 RESTRICT 类似,仅在检查约束的时机上有差异(多数数据库等效 RESTRICT)

如果需要移除外键,可通过 ALTER TABLE 实现:

sql 复制代码
ALTER TABLE `spaces`.`sys_admin` DROP FOREIGN KEY `fk_sys_department`;

定义外键的时候,一定要考虑好约束规则,推荐使用 CASCADE(级联操作:同步删除 / 更新),父表的记录被删除 / 修改时,子表中关联的记录会同步删除 / 修改,适合 "子表数据完全依赖父表" 的场景(比如订单完全属于某个用户,用户删除则订单也删除)。

sql 复制代码
-- 1. 创建子表,指定ON DELETE CASCADE + ON UPDATE CASCADE
CREATE TABLE orders_cascade (
    id INT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(20) NOT NULL UNIQUE,
    user_id INT,
    CONSTRAINT fk_orders_cascade_uid 
        FOREIGN KEY (user_id) 
        REFERENCES users (id)
        ON DELETE CASCADE  -- 级联删除
        ON UPDATE CASCADE  -- 级联更新
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 2. 插入子表数据
INSERT INTO orders_cascade (order_no, user_id) VALUES 
('ORD003', 1),  -- 关联张三
('ORD004', 1),  -- 关联张三
('ORD005', 2);  -- 关联李四

-- 3. 测试1:级联删除(删除张三id=1)
DELETE FROM users WHERE id=1;
-- 执行结果:成功删除用户张三
-- 查询子表:关联张三的2条订单也被同步删除
SELECT * FROM orders_cascade;  -- 仅剩下ORD005(关联李四)

-- 4. 测试2:级联更新(修改李四id=2改为20)
UPDATE users SET id=20 WHERE id=2;
-- 执行结果:成功更新用户id
-- 查询子表:ORD005的user_id同步改为20
SELECT * FROM orders_cascade;  -- ORD005的user_id=20

一句话总结,如果不是遇到超级大的高并发场景尽量养成在关联表中定义外键约束的习惯,很多同学很容易忽略在关联表中定义外键约束的重要性,从而导致数 据缺失,影响系统的可靠性。

相关推荐
倔强的石头_2 小时前
融合数据库架构实践:关系型、JSON与全文检索的“一库多能”深度解析
数据库
兆子龙2 小时前
【React】19 深度解析:掌握新一代 React 特性
前端·架构
无双_Joney2 小时前
心路散文 - 转职遇到AI浪潮,AIGC时刻人的价值是什么?
前端·后端·架构
嚴寒4 小时前
前端配环境配到崩溃?这个一键脚手架让我少掉了一把头发
前端·react.js·架构
老迟聊架构4 小时前
说说Vibe Coding的适应范围
人工智能·程序员·架构
星辰员4 小时前
KingbaseES数据库:ksql 命令行用户与权限全攻略,从创建到删除
数据库
看晴天了4 小时前
新框架electronbun项目入门指南,解决electron体积大的难题,Electrobun:Electron 的轻量级革命 —— 12MB 应用 +
前端·架构
sTone873754 小时前
web后端开发概念: VO 和 PO
java·后端·架构
裴云飞4 小时前
Compose原理十一之手势协程化,从回调到挂起的桥接艺术
架构