MySQL高级部分学习
零碎知识
基础知识
添加数据
指定字段:
1 | INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...); |
全部字段:
1 | INSERT INTO 表名 VALUES (值1, 值2, ...); |
批量添加数据:
1 | INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...); |
更新和删除数据
修改数据:
1 | UPDATE 表名 SET 字段名1 = 值1, 字段名2 = 值2, ... [ WHERE 条件 ]; |
删除数据:
1 | DELETE FROM 表名 [ WHERE 条件 ]; |
查询数据
1 | SELECT |
分页查询:起始索引从0开始,起始索引 = (查询页码 - 1) * 每页显示记录数
UNION ALL 会有重复结果,UNION 不会
联合查询比使用or效率高,不会使索引失效
SQL语句的执行顺序
函数
字符串函数
数值函数
日期函数
流程控制函数
子查询
列子查询
行子查询
表子查询
事务相关知识
事务四大特性ACID
- A 原子性(Atomicity) 一个事务的所有操作,要么全部完成,要么都没完成,不能结束在中间环节。如果事务在执⾏过程中发生错误,会被回滚到事务开始之前的状态
- C 一致性(Consistency) 在事务开始之前以及事务结束之后,数据库的完整性不能被破坏
- I 隔离性(Isolation) 允许多个并发事务同时对数据进⾏修改和读写的能⼒,它可以防止由于多个事务并发执⾏时由于交叉执行而导致的数据不一致
- D 持久性(Durability) 事务处理结束了以后,对数据的修改是永久的,即使是发生了系统故障,数据也不会丢失
举个例子:
A向B转账500,转账成功,A扣除500元,B增加500元,原子操作体现在要么都成功,要么都失败
在转账的过程中,数据要一致,A扣除了500,B必须增加500
在转账的过程中,隔离性体现在A像B转账,不能受其他事务干扰
在转账的过程中,持久性体现在事务提交后,要把数据持久化(可以说是落盘操作)
四大隔离级别
术语说明: (这三个可以称作并发事务所带来的问题)
- 脏读: 一个事务读取另一个事务还没有提交的数据
- 不可重复读: 同一个事务内,两个相同的查询返回了不同的结果
- 幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了 “幻影”。
RU 读不提交(Read Uncommited) 在该隔离级别下,事务之间完全不隔离,会产生脏读,一般情况不会使用
RC 读提交(Read Commited) 本事务读取到的是其它事务提交的最新数据,但有一个问题在同一个事务中,前后两相同的select可能会读到不同的结果
RR 可重复读(Repeatable Read) 在同一个事务中,select的结果是事务开始时时间点的状态,因此,同一个事务同样的select操作可以读到一致的结果
serializable串行化读操作会隐式获取共享锁,保证不同事务之间的互斥
⭐⭐问题:RR 可重复读为什么可以解决不可重复读,却不能解决幻读⭐⭐
答:对于”可重复读(Repeatable Read)”隔离级别,它确实可以解决”不可重复读(Non-repeatable Read)”问题,因为在这个隔离级别下,事务在执行期间看到的数据保持一致,即使其他事务在这段时间内进行了更改,也不会影响当前事务。
解决”不可重复读”问题的关键是行级锁定。在”可重复读”隔离级别下,读取的数据行会被锁定,确保其他事务不能修改或删除这些数据,从而保持了数据的一致性,防止了其他事务的干扰。
然而,”可重复读”隔离级别并不能完全解决”幻读”问题。”幻读”问题是由于其他事务在当前事务执行期间插入或删除了符合当前事务查询条件的新数据,导致查询结果发生变化。在”可重复读”隔离级别下,行级锁只能保护已有数据的读取,但对于新数据的插入和删除并不会进行锁定。因此,其他事务仍然可以插入或删除新数据,从而导致当前事务的查询结果发生变化,产生了”幻读”问题。
要解决”幻读”问题,需要进一步升级到更高的隔离级别,如”串行化(Serializable)”。在”串行化”隔离级别下,事务会锁定整个范围,包括已有数据和符合查询条件的新数据,确保其他事务不能插入或删除这些数据,从而完全消除了”幻读”问题。但是,”串行化”隔离级别的性能较低,因为它限制了并发性,所以在实际应用中需要权衡隔离级别和性能的需求。
存储引擎
MySQL体系结构
MySQL的基本架构图:
分析器用于判断你的SQL语句是否有错,优化器可以处理到底使用哪些索引,和先连接哪些表
连接池
SQL接口
SQL
接口会作为客户端连接传递SQL
语句时的入口,并且作为数据库返回数据时的出口
他的作用是将进来的SQL语句进行判断是哪一类的语句,比如是查询还是更新等,将其交给解析器;
同时他还作为结果的出口,后续的查询会查出来一条一条的数据,是SQL接口在最后进行结果集的封装。
解析器
解析器这一步的作用主要是为了验证SQL
语句是否正确,以及将SQL
语句解析成MySQL
能看懂的机器码指令。
类似于Java中将.java文件转换为.class文件的过程
优化器
优化器的主要职责在于生成执行计划,比如选择最合适的索引,选择最合适的join
方式等,最终会选择出一套最优的执行计划。
缓存
主要分为了读取缓存与写入缓冲,读取缓存主要是指select
语句的数据缓存,当然也会包含一些权限缓存、引擎缓存等信息,但主要还是select
语句的数据缓存,MySQL
会对于一些经常执行的查询SQL
语句,将其结果保存在Cache
中,因为这些SQL
经常执行,因此如果下次再出现相同的SQL
时,能从内存缓存中直接命中数据,自然会比走磁盘效率更高。
示例图:
包括四个层:
- 连接层 最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程 池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务 器也会为安全接入的每个客户端验证它所具有的操作权限。
- 服务层 第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部 分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如 过程、函数等。在该层,服务器会解 析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等, 最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大, 这样在解决大量读操作的环境中能够很好的提升系统的性能。
- 引擎层 存储引擎层, 存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通 信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库 中的索引是在存储引擎层实现的。
- 存储层 数据存储层, 主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询 日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是 基于库的,所以存储引擎也可被称为表类型。
存储引擎类型
InnoDB
- 特点
DML操作遵循ACID模型,支持事务;
行级锁,提高并发访问性能;
支持外键FOREIGN KEY约束,保证数据的完整性和正确性;
- 文件
xxx.ibd:xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结 构(frm-早期的 、sdi-新版的)、数据和索引。
- 逻辑存储结构
- 表空间 : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以 包含多个Segment段。
- 段 : 表空间是由各个段组成的, 常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管 理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。
- 区 : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为 16K, 即一个区中一共有64个连续的页。
- 页 : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默 认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
- 行 : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时 所指定的字段以外,还包含两个隐藏字段(后面会详细介绍)。
MyISAM
- 特点
不支持事务,不支持外键
支持表锁,不支持行锁
访问速度快
- 文件
xxx.sdi:存储表结构信息
xxx.MYD: 存储数据
xxx.MYI: 存储索引
Memory
Memory引擎的表数据时存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为 临时表或缓存使用。
- 特点
内存存放
hash索引(默认)
- 文件
- xxx.sdi:存储表结构信息
三种常见存储引擎区别与对比
重点对比InnoDB和MyISAM,分别是事务,外键和锁机制的区别。
索引⭐⭐
索引概述
索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构 上实现高级查找算法,这种数据结构就是索引。
优势 | 劣势 |
---|---|
提高数据检索的效率,降低数据库 的IO成本 | 索引列也是要占用空间的。 |
通过索引列对数据进行排序,降低 数据排序的成本,降低CPU的消 耗。 | 索引大大提高了查询效率,同时却也降低更新表的速度, 如对表进行INSERT、UPDATE、DELETE时,效率降低。 |
索引结构
MySQL默认是B+Tree结构
B-Tree&B+Tree
B-Tree图:
B+Tree图:
B+Tree 与 B-Tree相比,主要有以下三点区别:
- 所有的数据都会出现在叶子节点。
- 叶子节点形成一个双向链表。
- 非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点 的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能,利于排序。
hash
特点:
Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,< ,…)
无法利用索引完成排序操作
查询效率高,通常(不存在hash冲突的情况)只需要一次检索就可以了,效率通常要高于B+tree索 引
思考题
为什么InnoDB存储引擎选择使用B+tree索引结构?
- 相对于二叉树,层级更少,搜索效率高; (B+树三层就能存放2200w的数据)
- 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
- 相对Hash索引,B+tree支持范围匹配及排序操作;
索引分类
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对于表中主键创建的索引 | 默认自动创建, 只能有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键词,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
分类 | 含义 | 特点 |
---|---|---|
聚集索引 | 将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
二级索引 | 将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引。
- 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
- 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索 引。
执行流程
执行SQL语句的具体流程如图:
- 先在二级索引处查到name=Arm
- 再把在二级索引处查到的对应的id通过回标查询来到聚集索引处
- 得到id在通过聚集索引查到行数据
回表查询: 这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询。
思考题
以下两条SQL语句,那个执行效率高? 为什么?
A. select * from user where id = 10 ;
B. select * from user where name = ‘Arm’ ;
备注: id为主键,name字段创建的有索引;
解答: A 语句的执行性能要高于B 语句。 因为A语句直接走聚集索引,直接返回数据。 而B语句需要先查询name字段的二级索引,然 后再查询聚集索引,也就是需要进行回表查询。
索引语法
- 创建索引
创建的索引名一般命名为idx_表名_字段名
1 | CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name,... ) ; |
- 查看索引
1 | SHOW INDEX FROM table_name ; |
- 删除索引
1 | DROP INDEX index_name ON table_name ; |
SQL性能分析
可通过查看SQL执行频率,慢查询日志,profile详情和explain方法来分析哪个SQL语句的执行速度慢。
explain(重点掌握)
Explain 执行计划中各个字段的含义:
字段 | 含义 |
---|---|
id | select查询的序列号,表示查询中执行select子句或者是操作表的顺序 (id相同,执行顺序从上到下;id不同,值越大,越先执行)。 |
select_type | 表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接 或者子查询)、PRIMARY(主查询,即外层的查询)、 UNION(UNION 中的第二个或者后面的查询语句)、 SUBQUERY(SELECT/WHERE之后包含了子查询)等 |
type | 表示连接类型,性能由好到差的连接类型为NULL、system、const、 eq_ref、ref、range、 index、all 。 |
possible_key | 显示可能应用在这张表上的索引,一个或多个。 |
key | 实际使用的索引,如果为NULL,则没有使用索引。 |
key_len | 表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长 度,在不损失精确性的前提下, 长度越短越好 。 |
rows | MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值, 可能并不总是准确的。 |
filtered | 表示返回结果的行数占需读取行数的百分比, filtered 的值越大越好。 |
extra | 额外信息,例如Using index等 |
🟡🟢🔴补充:
在使用 EXPLAIN
关键字分析 MySQL 查询计划时,以下字段是重点关注的,它们能够提供关于查询执行的重要信息:
type: 连接类型。它指示了表连接的方法,是最重要的字段之一。关注它可以了解查询是否高效。常见类型按效率从高到低排序:
const
/system
:这种类型的连接意味着 MySQL 服务器可以使用表中的唯一键(通常是主键)找到一行记录。在某些情况下,MySQL 可以通过检查索引条目而不是实际的行来返回结果,这被称为“系统”连接类型。eq_ref
:当通过唯一索引(例如主键或唯一索引)进行等值匹配时,会使用这种连接类型。这意味着在连接操作中,对于每一条记录,都会有一个匹配项。ref
:当通过非唯一索引进行等值匹配时使用。这意味着对于每一条记录,可能存在多条匹配项。range
:当查询使用了索引并且涉及到范围查询(例如BETWEEN
、<
、>
等)时使用。这种类型允许 MySQL 服务器仅检索索引范围内的记录。index
:当查询只使用索引而不需要访问实际的数据行时,即所谓的“覆盖索引”。这意味着所有的查询条件和需要返回的列都在同一个索引中。ALL
:当 MySQL 不使用索引而是对整个表进行全表扫描时使用。这意味着 MySQL 将遍历整个表来查找满足条件的记录。
其中,
ALL
表示全表扫描,通常需要优化。key: 实际使用的索引。这个字段告诉你查询实际使用了哪个索引,如果为
NULL
,说明没有使用索引,可能需要添加或修改索引以提高查询性能。rows: 估计需要读取的行数。这个字段表示优化器估算需要扫描的行数。行数越少,查询通常越高效。如果行数很大,可能需要重新考虑查询方式或索引。
Extra: 附加信息。这个字段提供了关于查询执行的额外信息,常见的值有:
Using where
: 表示使用了 WHERE 条件进行过滤。Using index
: 表示使用了覆盖索引,查询只访问了索引树而没有访问数据行。Using temporary
: 表示查询使用了临时表,通常意味着需要优化。Using filesort
: 表示 MySQL 需要额外的排序操作,通常需要优化。
这四个字段能够帮助你迅速识别查询的主要问题和潜在的优化机会。其他字段(如 id
、select_type
、possible_keys
、key_len
和 ref
)也提供有价值的信息,但在初步分析时,上述四个字段通常是最先关注的重点。
使用规则
最左前缀法则
针对联合索引而言,联合索引字段中的第一个字段必须存在(不需要指定位置),若不存在第一个字段则整个索引失效,若后续其他字段中跳过了某个字段,则后续的部分索引失效
举例:
创建下列三个字段的联合索引
1 | CREATE INDEX idx_sku_price_num_weight ON tb_sku(price,num,weight); |
正常查询:
1 | SELECT * FROM tb_sku WHERE price=40625 AND num=10000 AND weight =10 |
跳过中间的字段后,后续字段索引会失效
1 | EXPLAIN SELECT * FROM tb_sku WHERE price=40625 AND weight =10 |
具体可通过
EXPLAIN
来查看是否使用了索引,看索引的长度来判断哪个字段索引被使用了
补充
上述的最左前缀法则针对的是where
后的条件,在where
后的条件中,只要出现了最左边的字段即可认为满足了最左前缀法则。而当在order by
之后的条件中,若是最左边的字段没有放在排序的第一位,即不按照顺序,这是不满足最左前缀法则的。综上,where
后条件不需要按顺序,order by
之后的条件需要按顺序。
具体示例看下文的order by优化的特殊情况
索引失效情况
此处举例大多都是会导致索引失效的例子
范围查询特殊情况
如果出现范围查询,则>或<之后的索引会失效
举例:
1 | SELECT * FROM tb_sku WHERE price=40625 AND num>10000 AND weight =10 |
上述SQL中num>10000,导致后面的weight的索引失效,只有price和num的索引生效
可以把>,<修改为>=,<=,修改之后就能避免这种情况发生,如下:
1 | SELECT * FROM tb_sku WHERE price=40625 AND num>=10000 AND weight =10 |
索引列运算
不要在索引列上进行运算操作, 索引将失效。
举例:
1 | explain select * from tb_user where substring(phone,10,2) = '15'; |
字符串不加引号
字符串类型字段使用时,不加引号,索引将失效。
举例:
1 | explain select * from tb_user where phone = 17799990015; |
说明:其实可以理解为phone本身是varchar类型,这里做了隐式类型转换,也算的上是计算
模糊查询
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
举例:
1 | explain select * from tb_user where profession like '%工程'; |
or连接条件
用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。只有当or前后都有索引才能生效。
举例:
1 | explain select * from tb_user where id = 10 or age = 23; #其中age字段无索引 |
数据分布影响⭐
如果MySQL评估使用索引比全表更慢,则不使用索引。MySQL自己评判。
走不走索引取决于查询的数据占全表的多少,若查询的数据占了全表的大部分,则全表搜索更快,故选择全表搜索,反之则相反。
SQL提示
此处的SQL提示,指的是我们去提示SQL,而不是SQL提示我们。
- 使用条件:
当一个字段既有单列索引又有联合索引时,MySQL默认使用联合索引,此时我们可以使用三种方式去提示SQL使用何种索引。
- 建议(建议仅仅只是建议,具体用不用还要MySQL自己来评估)
1 | explain select * from tb_user use index(idx_user_pro) where profession = '软件工程'; |
- 忽略(建议MySQL忽略某个索引,但是要不要忽略也是MySQL自己来评估)
1 | explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程'; |
- 强迫(MySQL必须采用所选的索引)
1 | explain select * from tb_user force index(idx_user_pro) where profession = '软件工程'; |
覆盖索引
覆盖索引指的是查询的字段全部都能一次查出,无需回表查询。
举例说明:
1 | explain select id, profession ,age, status, name from tb_user where profession = '软件工程' and age = 31 and status = '0' ; #需要回表查询 |
在这个SQL语句中,profession
,age
, status
这三个字段组成联合索引,而name
字段没有索引,所以在二级索引中profession
,age
, status
以及他们的叶子节点中存放的id
字段都是可以直接找到的,若这条语句不需要额外查询name
字段的话(如下SQL),那么这条语句就是覆盖索引,因为他不需要回表查询,但因为有了name
字段,所以不光要在二级索引中查,还要带着id
去聚集索引中查,这就是回表查询。回表查询自然就会慢了。
1 | explain select id, profession ,age, status from tb_user where profession = '软件工程' and age = 31 and status = '0' ; #覆盖索引,不需要回表查询 |
尽量不要使用
select *
的原因就是查询全部字段很大可能会因为有些字段没有索引,而导致需要回表查询。
思考题
一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对以下SQL语句进行优化, 该如何进行才是最优方案:
1 | select id,username,password from tb_user where username = 'itcast'; |
答案: 针对于 username, password建立联合索引, sql为:
1 create index idx_user_name_pass on tb_user(username,password);这样可以避免上述的SQL语句,在查询的过程中,出现回表查询。
前缀索引
当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让 索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。降低索引体积,提高查询效率
语法:其中的n
是从头开始截取的位数
1 | create index idx_xxxx on table_name(column(n)) ; |
具体n
取多少,下列sql中选择性越接近1且截取长度越少的越好
1 | select count(distinct substring(email,1,n)) / count(*) from tb_user ; |
例如n取10时选择性为1,取9,8,7,6,5时选择性都为0.985,n取4时选择性为0.913,此时的n就应该n=5
- 执行流程:
如 index1 (即email整个字符串的索引结构),执行顺序是这样的:
- 从index1索引树找到满足索引值是
zhangssxyz@xxx.com
的这条记录,取得ID2的值 - 到主键上查到主键值是ID2的行,判断email的值是正确的,将这行记录加入结果集!
- 取index1索引树上刚刚查到的位置的下一条记录,发现已经不满足email=
hangssxyz@xxx.com
的条件了,循环结束, 这个过程中,只需要回主键索引取一次数据,所以系统认为只扫描了一行。
如 index2 (即email(6)索结构) ,执行顺序是这样的:
- 从index2索引树找到满足索引值是’zhangs的记录,找到的第一个是ID1;
- 到主键上查到主键值是ID1的行,判断出email的值不是
zhangssxyz@xxx.com
,这行记录丢弃 - 取index2上刚刚查到的位置的下一条记录,发现仍然是hangs,取出ID2,再到ID索引上取整行然后判断,这次值对了,将这行记录加入结果集;
- 重复上一步,直到在idxe2上取到的值不是zhangs时,循环结束
在这个过程中,要回主键索引取4次数据,也就是扫描了4行。
通过这个对比,你很容易就可以发现,使用前缀索引后,可能会导致查询语句读数据的次数变多
单列索引&&联合索引
单列索引:即一个索引只包含单个列。
联合索引:即一个索引包含了多个列。
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引, 而非单列索引。
联合索引的B+Tree图:
索引设计原则⭐
- 针对于数据量较大,且查询比较频繁的表建立索引。
数据量大指的是超过一百多万……
针对于常作为查询条件 (where)、排序(order by)、分组 (group by) 操作的字段建立索引。
尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
区分度高的字段例如用户的手机号,身份证号,区分度低的例如性别,用户的状态等
如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。
SQL优化⭐
插入数据
批量插入数据
1 | Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry'); |
每次insert都要建立sql连接,会浪费时间和性能
手动控制事务
1 | start transaction; |
MySQL默认是自动提交事务,所以每次insert都会重复的开启事务,关闭事务,浪费时间
主键顺序插入,性能要高于乱序插入
1 | 主键乱序插入 : 8 1 9 21 88 2 4 15 89 5 7 3 |
大批量插入数据
当插入的数据大于几百万条的时候再使用insert效果就不好了
客户端连接服务端时,加上参数 –local-infile
1
mysql –-local-infile -u root -p
设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
1
set global local_infile = 1;
下面这条sql从该tb_sku1.sql
文件中加载数据到表tb_sku
,以,
分隔字段,以换行为一行数据
1 | load data local infile '/root/sql/tb_sku1.sql' into table `tb_sku` fields terminated by ',' lines terminated by '\n'; |
SQL文件前几行代码如下:
主键优化
主键顺序插入,性能要高于乱序插入
页分裂:
页分裂是指当一个页面(Page)已经满了,但需要插入新的数据或更新已有数据,MySQL 就会将页面拆分成两个页面,以容纳新的数据。这样做可以保证数据的有序性和唯一性,但页分裂可能会产生一些影响,主要包括:
- 性能影响: 页分裂会涉及数据的重新组织和移动,这可能会导致数据库性能的下降,特别是在频繁发生页分裂的表上。页分裂涉及磁盘 I/O 操作,而这些操作通常比内存操作慢得多。
- 磁盘空间的浪费: 页分裂后,可能会导致数据页只有一部分被占用,而其他部分为空闲,从而浪费了一些磁盘空间。
- 索引碎片化: 主键通常是索引键,页分裂会导致索引的碎片化。这可能会使索引树更加深度,影响查询性能。
- 锁竞争增加: 页分裂涉及数据的重新组织,这可能会增加锁的竞争。在高并发的环境下,这可能会导致锁等待时间增加,进而影响数据库的响应性能。
页合并
索引设计原则
- 满足业务需求的情况下,尽量降低主键的长度。
- 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。
- 尽量不要使用UUID做主键或者是其他自然主键,如身份证号。
- 业务操作时,避免对主键的修改
order by优化
- Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序
- Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高
在explain执行计划的extra中尽量不要显示
Using filesort
,这代表性能相对不好
举例说明:
有如下这样一个索引,默认的都是asc升序排序
1 | create index idx_user_age_phone on tb_user(age, phone); |
在存在这个索引的情况下,走如下sql语句不会出现Using filesort,只会出现Using index
1 | select id, age, phone from tb_user order by age asc, phone asc; |
因为创建索引的时候默认就是升序排的索引,所以这样能不出现Using filesort,但要是其中一个是倒序查询,那么就会出现Using filesort;但是如果两个都是倒序查询,那么反而不会出现Using filesort,因为在查询B+Tree的时候反向检索了。
特殊情况:如果排序的时候将age和phone的顺序写反了,那么就不遵循最左前缀法则了,即使两个都是asc查询,那么也不会全部走索引,自然就会出现Using filesort。
🔴🟡🟢总结:
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则
- 尽量使用覆盖索引
- 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)⭐
- 如果不可避免出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)
group by优化
尽量不要出现 Using temporary
- 在分组操作时,可以通过索引来提高效率
- 分组操作时,索引的使用也是需要满足最左前缀法则的
⭐下面这条sql也是符合最左前缀法则的,profession和age是联合索引
1 | explain select age , count(*) from tb_user where profession = '软件工程' group by age ; |
limit优化
对于大数据量的分页查询,如下,查询效率很低,越往后的数据查询时间越长
1 | select * from tb_sku limit 2000000 , 10 |
优化思路:
一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。
1 | select t.* from tb_sku t , (select id from tb_sku order by id limit 2000000,10) a where t.id = a.id; |
count优化
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高(前提是不适用where);
InnoDB 在执行 count(*) 时,需要把数据一行一行地从引擎里面读出来,然后累计计数。
优化方案:自己计数,如创建key-value表存储在内存或硬盘,或者是用redis,总的来说没有特别好的优化方式
count各种用法的性能:
- count(主键):InnoDB引擎会遍历整张表,把每行的主键id值都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加(主键不可能为空)
- count(字段):没有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加;有not null约束的话,InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加
- count(1):InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一层,放一个数字 1 进去,直接按行进行累加
- count(*):InnoDB 引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加
按效率排序:count(字段) < count(主键) < count(1) < count(*),所以尽量使用 count(*)
update优化
避免行锁升级为表锁
在InnoDB中的行锁是针对索引加的锁。
举例说明:
当两个事务同时开启时,如果操作的是不同的行(按照id来区分不同的行,如下SQL),那么是互不影响的。
1 | update course set name = 'javaEE' where id = 1 ; #事务一SQL |
1 | update course set name = 'javaEE' where id = 4 ; #事务二SQL |
但如果操作的虽然是不同的行,但是不是按有索引的字段来区分不同的行,例如按照name
这个没有索引的字段来区分不同的行,那么此时的行锁就会升级为表锁,大大降低了性能。
1 | update course set name = 'javaEE' where name = 'PHP' ; #事务一SQL |
1 | update course set name = 'javaEE' where name = 'Spring' ; #事务二SQL |
锁⭐⭐
概述
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、 RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
全局锁
概念
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。
在MySQL中,全局锁是针对整个MySQL实例的锁,而不是仅限于当前正在使用的数据库。
用途
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整 性。
- 不加锁的情况
- 加锁的情况(包含使用锁,备份,释放锁的语句)
在InnoDB引擎中,我们可以在备份时加上参数 –single-transaction 参数来完成不加锁的一致性数据备份。在InnoDB引擎中,不加锁还能保证一致性其实是通过快照读来实现的。
1 | mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql |
特点
数据库中加全局锁,是一个比较重的操作,存在以下问题:
- 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
- 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导 致主从延迟。
表级锁
表锁
语法:
- 加锁:lock tables 表名… read/write。
- 释放锁:unlock tables / 客户端断开连接 。
对于表锁,分为两类:
- 表共享读锁(read lock)
简称读锁
加锁后,所有客户端都能查询,但都不能增删改
- 表独占写锁(write lock)
简称写锁
加锁后,当前客户端可以增删改查,其他客户端什么都不能做
元数据锁(meta data lock,MDL)
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。
MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与 DDL冲突,保证读写的正确性。
这里的元数据,可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务 时,是不能够修改这张表的表结构的。
相关原理:
对应SQL | 锁类型 | 说明 |
---|---|---|
lock tables xxx read / write | SHARED_READ_ONLY / SHARED_NO_READ_WRITE | |
select 、select … lock in share mode | SHARED_READ | 与SHARED_READ、 SHARED_WRITE兼容,与 EXCLUSIVE互斥 |
insert 、update、 delete、select … for update | SHARED_WRITE | 与SHARED_READ、 SHARED_WRITE兼容,与 EXCLUSIVE互斥 |
alter table … | EXCLUSIVE | 与其他的MDL都互斥 |
当执行SELECT、INSERT、UPDATE、DELETE等语句时,添加的是元数据共享锁(SHARED_READ / SHARED_WRITE),之间是兼容的。
当执行SELECT语句时,添加的是元数据共享锁(SHARED_READ),会阻塞元数据排他锁 (EXCLUSIVE),之间是互斥的。
意向锁
在InnoDB引擎中,意向锁主要解决的问题是行锁和表锁之间冲突的问题
如下图所示
当客户端二,想对这张表加表锁时,会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就会从第一行数据,检查到最后一行数据,效率较低。
补充:在该场景中,事务一开启了一个事务并对ID为3的数据行加上了行锁,这意味着其他事务不能修改这一行数据,保证了事务一对该行数据的独占性。而另一个客户端想要在整张表上加一个表锁,这时就会与事务一的行锁产生冲突,因为事务一的行锁会阻止其他事务在这一行上加行锁,也会阻止其他事务在整张表上加表锁。
有了意向锁之后:
有了意向锁之后,线程B会通过意向锁来判断是否可以加表锁,大大提升效率
分类 :
意向共享锁(IS)
: 由语句select … lock in share mode添加 。 与表锁共享锁 (read)兼容,与表锁排他锁(write)互斥。意向排他锁(IX)
: 由insert、update、delete、select…for update自动添加 。与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。
一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。
行级锁
概念
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB存储引擎中。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。
解释:当事务对某行数据执行操作(例如UPDATE、DELETE)时,InnoDB会通过对涉及的索引项加锁来实现行锁。这是因为索引项存储了数据行的位置信息,而不需要直接访问实际数据。其他事务想要修改相同数据行时,会尝试在相同的索引项上加锁,从而产生锁竞争。
行锁
行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。在 RC、RR隔离级别下都支持。
InnoDB实现了以下两种类型的行锁:
- 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
- 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
两种行锁的兼容性如下图:
常见的SQL语句,在执行时,所加的行锁如下:
SQL | 行锁类型 | 说明 |
---|---|---|
INSERT … | 排他锁 | 自动加锁 |
UPDATE … | 排他锁 | 自动加锁 |
DELETE … | 排他锁 | 自动加锁 |
SELECT(正常) | 不加任何锁 | |
SELECT … LOCK IN SHARE MODE | 共享锁 | 需要手动在SELECT之后加LOCK IN SHARE MODE |
SELECT … FOR UPDATE | 排他锁 | 需要手动在SELECT之后加FOR UPDATE |
⭐⭐
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
- InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。
间隙锁
间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。
临键锁
临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。 在RR隔离级别下支持。
InnoDB引擎✏️
逻辑存储结构
架构
内存结构
- 缓冲池
- Change Buffer
- 自适应hash索引
- Log Buffer
硬件结构
各种表空间,存储数据,表结构,索引等信息
还有双写缓冲区,redo Log 重做日志
后台线程
事务原理
讨论事务的四个特性是如何实现保证的:
- 原子性:一致性,持久性:redo log ,undo log
- 隔离性:锁,MVCC(多版本并发控制)
redo log
重做日志:redo log日志记录的是数据页的物理变化,服务宕机可用来同步数据
redo log保证了事务的持久性
undo log
回滚日志:undo log 不同,它主要记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据,比如我们删除一条数据的时候,就会在undo log日志文件中新增一条delete语句,如果发生回滚就执行逆操作
undo log保证了事务的原子性和一致性
MVCC
事务的隔离性是由锁和mvcc实现的。
其中mvcc的意思是多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段,第二个是undo log日志,第三个是readView读视图
隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id(事务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer(回滚指针),指向上一个版本的事务版本记录地址
undo log主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表
readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。如果是rc隔离级别,每一次执行快照读时生成ReadView,如果是rr隔离级别仅在事务中第一次执行快照读时生成ReadView,后续复用
https://www.bilibili.com/video/BV1Hr421p7EK/
https://juejin.cn/post/7016165148020703246
日志
错误日志
错误日志是 MySQL 中最重要的日志之一,它记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。
该日志是默认开启的,默认存放目录 /var/log/,默认的日志文件名为 mysqld.log 。
二进制日志
二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,但不包括数据查询(SELECT、SHOW)语句。
作用:
- 灾难时的数据恢复;
- MySQL的主从复制。在MySQL8版本中,默认二进制日志是开启着
MySQL服务器中提供了多种格式来记录二进制日志,具体格式及特点如下:
日志格式 | 含义 |
---|---|
STATEMENT | 基于SQL语句的日志记录,记录的是SQL语句,对数据进行修改的SQL都会记录在日志文件中。 |
ROW | 基于行的日志记录,记录的是每一行的数据变更。(默认) |
MIXED | 混合了STATEMENT和ROW两种格式,默认采用STATEMENT,在某些特殊情况下会自动切换为ROW进行记录。 |
查询日志
查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的SQL语句。默认情况下,查询日志是未开启的。
慢查询日志
慢查询日志记录了所有执行时间超过参数 long_query_time 设置值并且扫描记录数不小于min_examined_row_limit 的所有的SQL语句的日志,默认未开启。long_query_time 默认为10 秒,最小为 0, 精度可以到微秒。
主从复制
MySQL 主从复制的优点主要包含以下三个方面:
主库出现问题,可以快速切换到从库提供服务。
实现读写分离,降低主库的访问压力。
可以在从库中执行备份,以避免备份期间影响主库服务。
它的步骤是这样的:
第一:主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。
第二:从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。
第三:从库重做中继日志中的事件,将改变反映它自己的数据
分库分表
随着互联网及移动互联网的发展,应用系统的数据量也是成指数式增长,若采用单数据库进行数据存储,存在以下性能瓶颈:
IO瓶颈:热点数据太多,数据库缓存不足,产生大量磁盘IO,效率较低。 请求数据太多,带宽不够,网络IO瓶颈。
CPU瓶颈:排序、分组、连接查询、聚合统计等SQL会耗费大量的CPU资源,请求数太多,CPU出现瓶颈。
拆分方式
1. 垂直分库
垂直分库:以表为依据,根据业务将不同表拆分到不同库中。
特点:
每个库的表结构都不一样。
每个库的数据也不一样。
所有库的并集是全量数据。
2. 垂直分表
垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。
特点:
每个表的结构都不一样。
每个表的数据也不一样,一般通过一列(主键/外键)关联。
所有表的并集是全量数据。
3. 水平分库
水平分库:以字段为依据,按照一定策略,将一个库的数据拆分到多个库中。
特点:
每个库的表结构都一样。
每个库的数据都不一样。
所有库的并集是全量数据。
4. 水平分表
水平分表:以字段为依据,按照一定策略,将一个表的数据拆分到多个表中。
特点:
每个表的表结构都一样。
每个表的数据都不一样。
所有表的并集是全量数据。
主流方案
MyCat:属于中间件,代码无侵入,但是性能差
ShardingSphere:基于AOP实现,修改代码,性能强
MyCat
分片规则
范围: 指定一个范围来分片,例如根据某个字段的值在一定范围内进行分片。
取值: 按照某个字段的具体值进行分片。
枚举: 使用预定义的枚举值来分片。
一致性hash: 通过一致性哈希算法来进行分片,确保数据均匀分布。
固定分片hash算法: 使用固定的哈希算法进行分片,类似于一致性哈希,但可能不具备数据均匀分布的特性。
字符串hash解析算法: 对字符串字段进行哈希解析,来决定数据的分片位置。
按天分片: 根据时间进行分片,以天为单位。
按自然月分片: 根据自然月进行分片,以月为单位。
基本工作原理
- SQL解析
当MyCat接收到客户端的SQL请求时,首先进行SQL解析。SQL解析包括语法解析和语义解析两个部分:
- 语法解析:分析SQL语句的结构,确定SQL的类型(如SELECT、INSERT、UPDATE、DELETE等),以及解析出各个组成部分(如表名、字段名、条件等)。
- 语义解析:基于语法解析的结果,进一步理解SQL语句的具体含义,比如识别出涉及的表和字段,以及具体的查询或操作条件。
- 路由
解析完SQL语句后,MyCat需要决定将SQL请求路由到哪些数据库和表。路由规则通常根据用户定义的分片规则来确定。这些规则可以基于范围、哈希、时间等多种方式。
- 范围分片:根据字段值的范围决定路由目标。
- 哈希分片:根据哈希算法,将数据均匀分布到多个分片中。
- 时间分片:根据时间(如按天、按月)将数据分布到不同的分片。
- 分片
在路由确定之后,MyCat将SQL请求分解为多个针对具体分片的子请求。例如,一个查询可能需要同时查询多个分片的数据,然后将结果合并。
- 执行
MyCat将分解后的子请求发送到相应的数据库实例上执行。MyCat支持多种数据库,如MySQL、Oracle等。每个子请求在相应的数据库实例上独立执行,获取各自的结果。
- 结果合并
对于查询操作,MyCat需要将来自不同分片的查询结果合并为一个整体的结果集。这包括去重、排序、聚合等操作,以确保最终结果与在单个数据库上执行的结果一致。
- 缓存和优化
为了提升性能,MyCat支持多种缓存机制。例如,可以缓存常用的查询结果,减少对数据库的直接访问。此外,MyCat还进行了一些优化,如批量操作优化、事务管理优化等。
- 管理和监控
MyCat提供了丰富的管理和监控功能。用户可以通过管理工具配置分片规则、数据源、用户权限等。同时,监控工具可以实时监控MyCat的运行状态,包括SQL执行情况、连接数、线程池状态等,帮助用户及时发现和解决问题。
ShardingSphere
本质上还是去改写SQL,让数据库表指定到相应的数据库表去
读写分离
原理就是上面的 Binlog
开发规范
- 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明:1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。3)无用字段增加网络消耗,尤其是 text 类型的字段。
在数据库查询操作中,使用
SELECT * FROM table_name
这种形式来查询表的所有字段确实是一种常见的做法,尤其是在开发初期或进行快速测试时。然而,随着项目的发展和数据量的增大,这种做法可能会带来一些问题,特别是性能和资源管理方面的问题。以下是对第一点“增加查询分析器解析成本”的详细解释:增加查询分析器解析成本
当SQL语句中的
*
被用来代替具体的字段列表时,数据库的查询分析器(Query Analyzer)需要执行额外的工作来确定该表包含的所有字段。这个过程包括但不限于:
- 元数据检索:查询分析器需要从系统目录(或称为系统表,存储了数据库结构信息的地方)中检索表的元数据,以获取所有字段的信息。
- 字段解析:一旦获取到所有字段信息,查询分析器需要解析这些字段,确定它们的数据类型、长度等属性,以便于后续的数据处理。
- 优化器决策:基于字段信息,查询优化器需要决定如何最有效地执行查询,这可能涉及到索引选择、数据读取路径等决策,而更多的字段意味着更复杂的决策过程。
相比之下,如果在
SELECT
语句中明确列出所需字段,查询分析器可以直接使用这些信息,避免了上述额外步骤,从而减少了解析成本。此外,明确列出字段也有助于:
- 减少网络传输开销:只传输所需的字段数据,对于大型字段如
text
或blob
类型尤其重要,可以显著减少网络传输量。- 提高可维护性和可读性:明确的字段列表使代码更易于理解和维护,特别是在团队协作环境中,其他开发者能更快地理解查询的目的。
- 防止意外数据泄露:如果表中包含敏感信息,显式列出字段可以帮助防止无意中泄露这些信息。
因此,尽管在某些情况下使用
*
可能看起来更方便,但从长期和性能的角度来看,明确指定字段列表通常是一个更好的实践。
- 【强制】如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
在数据库设计中,选择
CHAR
或VARCHAR
数据类型取决于具体的应用场景和需求。虽然VARCHAR
提供了更灵活的字符串长度管理,但在某些情况下,使用CHAR
定长字符串类型更有优势,尤其是在字符串长度几乎相等的情况下。以下是一些原因:
性能优化:由于
CHAR
是固定长度的字符串类型,数据库引擎可以更有效地处理和存储这些数据,因为不需要额外的空间来存储字符串的实际长度。这在大量数据操作时可能会带来性能上的提升。空间效率:虽然
VARCHAR
只存储实际使用的字符数加上长度信息,但如果大多数字符串长度接近或等于预设的最大长度,那么CHAR
类型可能更节省空间。这是因为VARCHAR
需要额外的字节来存储字符串的长度,而CHAR
则不需要。简化查询:在处理固定长度的字符串时,如电话号码、邮政编码等,使用
CHAR
可以简化查询过程,因为数据库引擎不需要去解析每个字段的长度,直接按照固定长度读取即可。一致性:对于需要保证数据格式一致性的字段,如身份证号、学号等,使用
CHAR
可以确保所有记录都具有相同的长度,即使某些记录的某些部分为空,也会通过空格填充至指定长度,从而保持数据的一致性和可读性。然而,
VARCHAR
也有其独特的优势,比如在处理长度变化较大的文本数据时更为合适,因为它能够更高效地利用存储空间。因此,在设计数据库表结构时,应根据实际需求权衡使用CHAR
还是VARCHAR
。例如,对于短小且长度固定的字段,如电话号码、邮政编码,使用CHAR
可能更合适;而对于长度不一的文本描述,如个人简介、评论等,则更适合使用VARCHAR
。