MySQL 便利贴(基础)

FROM 《姜承尧的 MySQL 实战宝典》

数字类型

整型的 signed 和 unsigned

不要刻意使用 unsigned,使用 unsigned 可能返回不想要的结果。

unsigned 和 unsigned 数值相减之后依然为 unsigned,否则就会报错。

为了避免这个错误,需要对数据库参数 sql_mode 设置为 NO_UNSIGNED_SUBTRACTION,允许相减的结果为 signed。

浮点类型和高精度型

Float 和 Double 不是高精度,真实的生产环境中不推荐使用。

高精度 DECIMAL 类型可以使用,当声明该类型列时,可以(并且通常必须要)指定精度和标度

金额字段的设计并不推荐使用 DECIMAL 类型,而更推荐使用 INT 整型类型

整型类型与自增设计

  • 自增整型做主键,一律使用 BIGINT
  • 自增值并不持久化,可能会有回溯现象(MySQL 8.0 版本前)

达到自增整型类型的上限值时,再次自增插入,MySQL 数据库会报重复错误。

不建议使用整型类型做主键,更为推荐的是字符串类型。

资金字段设计

不推荐用 DECIMAL 类型,而是更推荐将 DECIMAL 转化为整型类型,推荐使用用分单位存储,而不是用元单位存储。原因:

  • 所有金额相关字段都是定长字段,占用 8 个字节,存储高效
  • 直接通过整型计算,效率更高

字符串类型

CHAR 和 VARCHAR

CHAR(N) 用来保存固定长度字符,N 的范围是 0 ~ 255。

VARCHAR(N) 用来保存变长字符,N 的范围为 0 ~ 65536。

超出 65536 个字符的情况下,可以考虑使用更大的字符类型 TEXT 或 BLOB,两者最大存储长度为 4G,其区别是 BLOB 没有字符集属性,纯属二进制存储。

在 MySQL 数据库下,绝大部分场景使用类型 VARCHAR 就足够了。

字符集

在表结构设计中,除了将列定义为 CHAR 和 VARCHAR 用以存储字符以外,还需要额外定义字符对应的字符集,因为每种字符在不同字符集编码下,对应着不同的二进制值。

从底层存储内核看,在多字节字符集下,CHAR 和 VARCHAR 底层的实现完全相同,都是变长存储

包括 MySQL 8.0 版本在内,字符集默认设置成 UTF8MB4,8.0 版本之前默认的字符集为 Latin1。

常见的字符集有 GBK、UTF8,推荐把 MySQL 的默认字符集设置为 UTF8MB4鉴于目前默认字符集推荐设置为 UTF8MB4,所以在表结构设计时,可以把 CHAR 全部用 VARCHAR 替换,底层存储的本质实现一模一样。

排序规则

比较和排序字符串的一种规则,每个字符集都会有默认的排序规则。用命令 SHOW CHARSET 来查看。

排序规则

  • _ci 结尾,表示不区分大小写(Case Insensitive)
  • _cs 表示大小写敏感
  • _bin 表示通过存储字符的二进制进行比较

需要注意的是,比较 MySQL 字符串,默认采用不区分大小的排序规则。 绝大部分业务的表结构设计无须设置排序规则为大小写敏感

正确修改字符集

# 错误,对于已经存在的列,其默认字符集并不做修改
ALTER TABLE table_name CHARSET utf8mb4;

# 正确
ALTER TABLE table_name CONVERT TO CHARSET utf8mb4;

用户性别设计

在 MySQL 8.0 版本之前,可以使用 ENUM 字符串枚举类型,只允许有限的定义值插入。

自 MySQL 8.0.16 版本开始,数据库原生提供 CHECK 约束功能,可以方便地进行有限状态列类型的设计。

CONSTRAINT `user_chk_1` CHECK (((`sex` = _utf8mb4'M') or (`sex` = _utf8mb4'F')))

账户密码存储设计

从合规性角度看,所有用户隐私字段都需要加密。

动态加盐 + 非固定加密算法。

# 密码存储格式可以采取
$动态盐$加密算法$加密后的字符串

日期类型

常见的日期类型有 YEAR、DATE、TIME、DATETIME、TIMESTAMEP,常见使用的日期类型为DATETIME 和 TIMESTAMP。

DATETIME

类型 DATETIME 最终展现的形式为:YYYY-MM-DD HH:MM:SS,固定占用 8 个字节。

MySQL 5.6 版本开始,DATETIME 类型支持毫秒,DATETIME(N) 中的 N 表示毫秒的精度。

常见的函数 NOW、SYSDATE 也支持精确到毫秒。

TIMESTAMP

# 指定默认时间
DEFAULT CURRENT_TIMESTAMP(N)

# 自动记录当前时间
DEFAULT CURRENT_TIMESTAMP(N) ON UPDATE CURRENT_TIMESTAMP(N)

从 MySQL 5.6 版本开始,TIMESTAMP 占用 4 个字节,带毫秒时占用 7 个字节。

优点是可以带有时区属性,本质上是从毫秒转化而来,实际存储的内容为‘1970-01-01 00:00:00’到现在的毫秒数。

DATETIME vs TIMESTAMP

建议使用类型 DATETIME。 对于时区问题,可以由前端或者服务这里做一次转化。

TIMESTAMP 的劣势

  • 性能不如 DATETIME: DATETIME 不存在时区转化问题
  • 性能抖动: 海量并发时,存在性能抖动问题
  • 上限值 2038 年很快就会到来

优化 TIMESTAMP 可以使用显式的时区,而不是操作系统时区。(在配置文件中显示地设置时区)

表结构设计规范:每条记录都要有一个时间字段

在做表结构设计规范时,强烈建议每张业务核心表都增加一个 DATETIME 类型的 last_modify_date 字段,并设置修改自动更新机制, 即便标识每条记录最后修改的时间。


非结构存储

用户登录设计

在数据库中,JSON 类型比较适合存储一些修改较少、相对静态的数据,比如用户登录信息的存储。

当 JSON 数据量非常大,用户希望对 JSON 数据进行有效检索时,可以利用 MySQL 的函数索引功能对 JSON 中的某个字段进行索引。

# 创建虚拟列 cellphone,数据来源于函数 loginInfo->>"$.cellphone"
# 也就是 JSON 中存储的 cellphone
ALTER TABLE UserLogin ADD COLUMN cellphone VARCHAR(255) AS (loginInfo->>"$.cellphone");

# 虚拟列的基础上创建一个唯一索引 idx_cellphone
ALTER TABLE UserLogin ADD UNIQUE INDEX idx_cellphone(cellphone);

用户画像设计

强烈推荐用 JSON 类型在数据库中存储用户画像信息,并结合 JSON 数组类型和多值索引的特点进行高效查询。

举例,将标签单独定义一个集合,记录用户画像时保存标签集合的编号,而不是使用分隔符存储字符串。

{MySQL 8.0.17 版本开始支持 Multi-Valued Indexes,用于在 JSON 数组上创建索引,并通过函数 member of、json_contains、json_overlaps 来快速检索索引数据。}

# 示例
# 创建多值索引
ALTER TABLE UserTag
ADD INDEX idx_user_tags ((cast((userTags->"$") as unsigned array)));

# 根据编号查询对应标签(1 个标签使用 MEMER OF)
EXPLAIN SELECT * FROM UserTag 
WHERE 10 MEMBER OF(userTags->"$")\G

# 查询多个标签,使用 JSON_CONTAINS(命中全部)
SELECT * FROM UserTag 
WHERE JSON_CONTAINS(userTags->"$", '[2,10]');

# 查询多个标签,使用 JSON_OVERLAP(命中任意个)
EXPLAIN SELECT * FROM UserTag 
WHERE JSON_OVERLAPS(userTags->"$", '[2,3,10]')\G

表结构设计:忘记范式准则

在真实业务中,不必严格遵守三范式的要求。而且有时为了性能考虑,还可以进行反范式的设计,比如在数据仓库领域。

工程上的表结构设计实战

  • 每张表一定要有一个主键(方法有自增主键设计、UUID 主键设计、业务自定义生成主键)
  • 消除冗余数据存在的可能

一张表有主键,就已经直接满足一范式的要求了,使用 BIGINT 的自增类型作为主键的设计仅仅适合非核心业务表,比如告警表、日志表等。真正的核心业务表,一定不要用自增键做主键。原因如下:

  • 自增存在回溯问题
  • 自增值在服务器端产生,存在并发性能问题
  • 自增值做主键,只能在当前实例中保证唯一,不能保证全局唯一
  • 公开数据值,容易引发安全问题
  • MGR(MySQL Group Replication) 可能引起的性能问题
  • 分布式架构设计问题

自增值是在 MySQL 服务端产生的值,需要有一把自增的 AI 锁保护,若这时有大量的插入请求,就可能存在自增引起的性能瓶颈。想获得自增值的最大并发性能,把参数 innodb_autoinc_lock_mode 设置为2。由于其还存在上述 5 个问题。因此,在互联网海量并发架构实战中,更推荐 UUID 做主键或业务自定义生成主键

UUID主键设计

# Version 1规范,MySQL中 UUID 组成,时间计数的标准为 100ns
UUID = 时间低(4字节)- 时间中高+版本(4字节)- 时钟序列 - MAC地址

在存储时间时,UUID 是根据时间位逆序存储, 低时间低位存放在最前面,高时间位在最后,即 UUID 的前 4 个字节会随着时间的变化而不断“随机”变化,并非单调递增。而随机值在插入时会产生离散 IO,从而产生性能瓶颈。这也是 UUID 对比自增值最大的弊端。

MySQL 8.0 推出了函数 UUID_TO_BIN:

  • 通过参数将时间高位放在最前,解决了 UUID 插入时乱序问
  • 去掉了无用的字符串”-“,精简存储空间
  • 将字符串其转换为二进制值存储,空间最终从之前的 36 个字节缩短为了 16 字节

可以将主键修改为 BINARY(16),用于存储排序后的 16 字节的 UUID 值

MySQL 8.0 也提供了函数 BIN_TO_UUID,支持将二进制值反转为 UUID 字符串。

业务自定义生成主键

分布式数据库架构,仅用 UUID 做主键依然是不够的

分布式架构的核心业务表,推荐类似如下的设计:

PK = 时间字段 + 随机码(可选) + 业务信息1 + 业务信息2 ......

消除冗余和反范式设计

范式设计的目的就是通过依赖和传递依赖消除冗余。

随着 JSON 数据类型的普及,MySQL 在线业务也可以进行反范式的设计。

合理的冗余可以带来更高效的处理。


表压缩

页压缩

通常我们认为,如果一个页中存放的记录数越多,数据库的性能越高。这是因为数据库表空间中的页是存放在磁盘上,MySQL 数据库先要将磁盘中的页读取到内存缓冲池,然后以页为单位来读取和管理记录。一个页中存放的记录越多,内存中能存放的记录数也就越多,那么存取效率也就越高。若想将一个页中存放的记录数变多,可以启用压缩功能。

之所以不压缩单条记录或压缩表空间:

  • 压缩每条记录: 每次读写都要压缩和解压,过于依赖 CPU 的计算能力,性能会明显下降;另外,因为单条记录大小不会特别大,一般小于 1K,压缩效率也并不会特别好。
  • 压缩表空间: 压缩效率非常不错,但要求表空间文件静态不增长,这对基于磁盘的关系型数据库来说,很难实现。

压缩会增加 CPU 开销,同时也会降低 IO 开销。

MySQL 压缩表

COMPRESS 页压缩

MySQL 5.7 版本之前的页压缩功能。

将一个页压缩到指定比例大小,如果无法压缩到当前比例,就会多产生一个页来补充。

16K -> 8K (成功)
16K -> 2 * 8K (不成功)
# 启用 COMPRESS 页压缩
ROW_FORMAT=COMPRESS

# 压缩比例
KEY_BLOCK_SIZE

压缩前的页和压缩后的页同时存在内存缓冲中,导致内存开销增大,MySQL 性能退化。

TPC 压缩

前提 <当前操作系统支持文件空洞特性>

利用空洞压缩到文件系统的最小单位 4K

对数据库性能的侵入几乎无影响,TPC 压缩在内存中只有一个解压缩后的页。只有当这个页需要刷新到磁盘时,才会触发页压缩功能一次。

# 创建 TPC 压缩表
CREATE TABLE Transaction (

  transactionId BINARY(16) PRIMARY KEY,

  .....

)
COMPRESSION=ZLIB | LZ4 | NONE;

表压缩在业务上的使用

COMPRESS -> 性能不敏感的业务表:日志、监控、告警表等

TPC -> 较为核心的流水业务表

对于当前频繁使用的流水表(当日)不启用表压缩,对于历史流水表(往日)启用 TPC 压缩。

# 启用 TPC 表压缩,但只对后续新增的数据进行压缩
ALTER TABLE table COMPRESSION = ZLIB;

# 对整表进行压缩
ALTER TABLE table COMPRESSION=ZLIB;
OPTIMIZE TABLE table;

表的访问设计

通过 Memcached 协议访问

MySQL 5.6 版本开始支持通过插件 Memcached Plugin,以 KV 方式访问表,这时可以将 MySQL视作一个 Memcached KV 数据库。

对于数据的访问不再是通过 SQL 接口,而是通过 KV 数据库中常见的 get、set、incr 等请求。

开启流程:

  1. 开启 Memcached 插件
  2. 配置表与 KV 的映射关系
# 安装映射表
mysql> source MYSQL_HOME/share/innodb_memcached_config.sql

# 安装插件,默认会启动11211端口
mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so";

执行完上述操作后,会新增一个库 innodb_memcache,里面的表 containers 就是需要配置的KV映射表,示例:

INSERT INTO containers
VALUES ('User','test','user_id','user_id|cellphone|last_login','0','0','0','PRIAMRY')

使用时:

# 读取映射表User
mc.get('@@User')
mc.get('key1')

# 读取映射表sbtest1
mc.get('@@sbtest1')
mc.get('sb1_key1','aa|bbb|ccc')

通过 X Protocol 访问表

MySQL 5.7 版本开始原生支持 JSON 二进制数据类型,同时也提供将表格映射为一个 JSON 文档。

MySQL 也提供了 X Protocol 这样的 NoSQL 访问方式。

 X Protocol 通过新的 MySQL Shell 命令,需要单独安装。

# 通过新的 X Protocol 访问 MySQL 数据库
root@MBP-Windows:# mysqlsh root@localhost/test

若要通过 X Protocol 协议管理文档数据,也需要下载新的 MySQL Connector,并引入新的 X 驱动库。


索引:排序

索引是提升查询速度的一种数据结构

B+树索引结构

B+树是目前为止排序最有效率的数据结构。像二叉树,哈希索引、红黑树、SkipList,在海量数据基于磁盘存储效率方面远不如 B+ 树索引高效。

索引是对记录进行排序

优化 B+树索引插入的性能

平衡树查询高效,而在插入时的维护开销就大,但排序的开销其实并没有想象得那么大。

真正的开销在于 B+ 树索引的维护,保证数据排序,这里存在两种不同数据类型的插入情况:

  1. 数据顺序(或逆序)插入:B+ 树索引的维护代价非常小,叶子节点都是从左往右进行插入,比较典型的是自增 ID 的插入、时间的插入
  2. 数据无序插入:B+ 树为了维护排序,需要对页进行分裂、旋转等开销较大的操作,另外,即便对于固态硬盘,随机写的性能也不如顺序写,所以磁盘性能也会收到较大影响。比较典型的是用户昵称,每个用户注册时,昵称是随意取的,若在昵称上创建索引,插入是无序的,索引维护需要的开销会比较大

所以对于 B+ 树索引,在 MySQL 数据库设计中,仅要求主键的索引设计为顺序,比如使用自增,或使用函数 UUID_TO_BIN 排序的 UUID,而不用无序值做主键。

MySQL 中 B+ 树索引的设计与管理

# 查看每个索引信息的表
mysql.innodb_index_stats

不存在一张表中索引数量不能超过多少的说法!

# 查看未被使用的索引
sys.schema_unused_indexes

搜索并删除长期未被使用的索引来优化数据库。MySQL 8.0 推出了索引不可见功能,在删除前可以设置为对优化器不可见,观察业务是否收到影响。

ALTER TABLE t1 

ALTER INDEX idx_name INVISIBLE/VISIBLE;

索引组织表

数据存储有堆表和索引组织表两种方式。

  • 堆表中的数据无序存放, 数据的排序完全依赖于索引(Oracle、Microsoft SQL Server、PostgreSQL 早期默认支持的数据存储都是堆表结构)。
  • 索引组织表,数据根据主键排序存放在索引中,主键索引也叫聚集索引(Clustered Index)。在索引组织表中,数据即索引,索引即数据。MySQL InnoDB 存储引擎就是这样的数据组织方式

聚集索引的叶节点存的是主键和行记录。

二级索引

除了主键索引外,其他的索引都称之为二级索引(Secondeary Index) 或非聚集索引(None Clustered Index)。

二级索引也是 B+ 树索引,但它和主键索引不同的是叶子节点存放的是索引键值、主键值

通过二级索引只能定位主键值,需要额外再通过主键索引进行查询,才能得到最终的结果。这种“二级索引通过主键索引进行再一次查询”的操作叫作“回表”

索引组织表这样的二级索引设计有一个非常大的好处:若记录发生了修改,则其他索引无须进行维护,除非记录的主键发生了修改。

唯一索引可以理解为额外附加了一个单独的索引(只存放索引键值,目的就是用于检测是否唯一)

二级索引可能是比较顺序插入,也可能是完全随机的插入。

  • 要比较顺序,对聚集索引性能友好
  • 尽可能紧凑,对二级索引的性能和存储友好

函数索引

5.7 版本开始,MySQL 开始支持创建函数索引。

  • 优化业务 SQL 性能
  • 结合虚拟列使用

创建虚拟列,使用函数索引直接使用虚拟列。


组合索引

组合索引的排序按照前后出现顺序会不同。

例如 组合索引 (a, b)

# 正确
# where 中 and 条件的先后顺序对如何选择索引是无关的,优化器会去分析判断选用哪个索引
SELECT * FROM table WHERE a = ?
SELECT * FROM table WHERE a = ? AND b = ?
SELECT * FROM table WHERE b = ? AND a = ?

# 错误
SELECT * FROM table WHERE b = ?

因为索引是排序,而组合索引的排序是根据出现先后顺序进行的(最左原则),因而不存在 a 直接 搜索 b 是找不到结果的(无序的,可以类比基数排序)。

基于这个特性,对于一些有先后顺序的排序场景,可以通过组合索引一次获取。

{注:MySQL 8.0 支持倒序索引}

其次,二级组合索引的叶子节点,包含索引键值和主键值。这意味这不需要回表!

通过组合索引避免回表的优化技术也称为索引覆盖(Covering Index)


索引出错

MySQL 选择索引的原理

在关系型数据库中,B+ 树索引只是存储的一种数据结构,具体使用还要依赖数据库的优化器,优化器决定了具体某一索引的选择。优化器的选择是基于成本(cost),哪个索引的成本越低,优先使用哪个索引。

数据库 mysql 下的表 server_cost、engine_cost 则记录了对于各种成本的计算。

表 server_cost 记录了 Server 层优化器各种操作的成本,这里面包括了所有 CPU Cost:

  • disk_temptable_create_cost:创建磁盘临时表的成本,默认为20。
  • disk_temptable_row_cost:磁盘临时表中每条记录的成本,默认为0.5。
  • key_compare_cost:索引键值比较的成本,默认为0.05,成本最小。
  • memory_temptable_create_cost:创建内存临时表的成本:默认为1。
  • memory_temptable_row_cost:内存临时表中每条记录的成本,默认为0.1。
  • row_evaluate_cost:记录间的比较成本,默认为0.1。

表 engine_cost 记录了存储引擎层各种操作的成本,这里包含了所有的 IO Cost:

  • io_block_read_cost:从磁盘读取一个页的成本,默认值为1。
  • memory_block_read_cost:从内存读取一个页的成本,默认值为0.25。

未能使用创建的索引

优化器识别到了索引,但却采用全表扫描获取结果。

是因为优化器判断二级索引回表开销大于直接扫描。

索引创建在有限状态上

B+ 树索引通常要建立在高选择性的字段或字段组合上。

但在有些低选择性的列上,是有必要创建索引的,比如订单的交易状态。

通常订单状态绝大部分都是已完成,只有绝少部分因为系统故障原因,会在 15 分钟后还没有完成订单,因此订单状态是存在数据倾斜的。但 MySQL 并不知道状态存在数据倾斜,会认为是平均分布的。因此优化器会为了避免回表,采用全表扫描。

可以利用 MySQL 8.0 的直方图功能,创建一个直方图,让优化器知道数据的分布,从而更好地选择执行计划。

ANALYZE TABLE orders 

UPDATE HISTOGRAM ON o_orderstatus;

在创建完直方图后,MySQL会收集到字段的数值分布。


JOIN 连接

连接算法

MySQL 8.0 版本支持两种 JOIN 算法:

  • Nested Loop Join (查询数据量较小、语句相对简单,大多使用索引连接表之间的数据)
  • Hash Join (查询数据量较大,关联表的数量非常多)

Nested Loop Join

假设表 R 和 S 进行连接:

对于 R 中每一个符合匹配条件的 row r:
    查找 S 中索引值为 r 的索引 idx_s
    如果存在
        发送给 client 
# LEFT JOIN 左表 R 为驱动表
SELECT ... FROM R LEFT JOIN S ON R.x = S.x WEHRE ...

# RIGHT JOIN 右表 S 为驱动表
SELECT ... FROM R RIGHT JOIN S ON R.x = S.x WEHRE ...

# INNER JOIN 谁需要查询的数据量越少,谁就是驱动表
SELECT ... FROM R INNER JOIN S ON R.x = S.x WEHRE ...

Hash Join

Hash Join,用于两张表之间连接条件没有索引的情况。

诚然可以通过创建索引变成 Nested Loop Join 的情况,但:

  1. 如果有些列是低选择度的索引,那么创建索引在导入数据时要对数据排序,影响导入性能
  2. 二级索引有回表问题,若筛选的数据量比较大,则不如全表扫描
对于 R 中每一个符合匹配条件的 row r:
    以 r 为 key 创建哈希表 ht
对于 S 中每一个符合匹配条件的 row s:
    在哈希表 ht 中搜索 s
    如果存在
        发送给 client

在驱动表的选择上,都是较小的表作为驱动表。(添加表和查表都是O(1),哈希表小则内存占用少)

OLTP 业务写 JOIN

OLTP 业务完全可以大胆放心地写 JOIN,但是要确保 JOIN 的索引都已添加。

没事别瘠薄吃饱了撑的去拆 JOIN 语句,搞清楚自己的定位,优化器比大部分开发人员聪明。


子查询

MySQL 8.0 版本中,优化器会自动地将 IN 子查询优化,优化为最佳的 JOIN 执行计划。

依赖子查询的优化

在 MySQL 8.0 版本之前,MySQL 对于子查询的优化并不充分,在子查询的执行计划中会看到 DEPENDENT SUBQUERY 的提示,这表示是一个依赖子查询,子查询需要依赖外部表的关联。

看到这样的提示,就要警惕, 因为 DEPENDENT SUBQUERY 执行速度可能非常慢,大部分时候需要手动把它转化成两张表之间的连接。若看到依赖子查询的执行计划,记得先进行 SQL 重写优化。非 8.0 版本可以通过派生表的方式降低对于子查询表的扫描。


分区表

CREATE TABLE t (
    a INT,
    b INT,
    c DATETIME,
    d VARCHAR(32),
    e INT,
    PRIMARY KEY (a,b,c),
    KEY idx_e (e)
)

partition by range columns(c) (
    PARTITION p0000 VALUES LESS THAN ('2019-01-01'),
    PARTITION p2019 VALUES LESS THAN ('2020-01-01'),
    PARTITION p2020 VALUES LESS THAN ('2021-01-01'),
    PARTITION p9999 VALUES LESS THAN (MAXVALUE)
);

分区表就是把物理表结构相同的几张表,通过一定算法,组成一张逻辑大表,MySQL 中的分区表是把一张大表拆成了多张表,每张表有自己的索引,从逻辑上看是一张表,但物理上存储在不同文件中。

当前 MySQL 数据库支持的分区函数类型有 RANGE、LIST、HASH、KEY、COLUMNS。

无论选择哪种分区函数,都要指定相关列成为分区算法的输入条件,这些列就叫“分区列”(依照这个列和条件进行分区)。另外,在 MySQL 分区表中,主键也必须是分区列的一部分,不然创建分区表时会失败。

分区表:唯一索引

在 MySQL 数据库中,分区表的索引都是局部,而非全局。也就是说,索引在每个分区文件中都是独立的。分区表上的唯一索引必须包含分区列信息,否则创建会报错。

{理解:假设分区列为学校名,一个学号在同一个学校内(分区)唯一,但不同学校会出现相同学号的学生。因此唯一索引包含分区列是指,将(学校,学号)的组合看作唯一索引,而学号只在学校(分区)内唯一。所以最好还是通过 UUID,例如身份证来避免局部唯一问题。}

分区表的误区:性能提升

分区表技术不是用于提升 MySQL 数据库的性能,而是方便数据的管理,更多的是解决数据迁移和备份的问题。

分区表还会引入新的性能问题,比如非分区列的查询。非分区列的查询。即使分区列上已经创建了索引,但因为索引是每个分区文件对应的本地索引,所以要查询每个分区。