文章目录
- 1.数据库介绍
- 2.数据库的语法使用
- 数据库语法
- 数据库的执行过程
- 3.数据库的索引介绍
- 索引的介绍
- 索引创建注意点:
- 索引失效的情况
- 索引不适合哪些场景呢?
- 索引是不是建的越多越好呢?
- 索引的数据结构
- 为什么要用 B+ 树,而不用普通二叉树?
- 为什么用 B+ 树而不用 B 树呢?
- Hash 索引和 B+ 树索引区别是什么?
- 聚簇索引与非聚簇索引的区别?
- 4.数据库sql优化
- sql性能优化
- 分库分表
- 分库分表的动机
- 分库分表的策略
1.数据库介绍
数据库是一种用于组织、存储和管理数据的系统。它可以被视为一个电子化的文件柜,用于存储大量结构化或非结构化的信息,供后续检索、更新和管理。数据库系统通常由以下几个组件组成:
数据: 数据库存储的实际信息,可以是文本、数字、图像、音频等形式。数据按照一定的结构进行组织,以便于有效地管理和检索。
数据库管理系统(DBMS): 数据库管理系统是一个软件,负责管理数据库中的数据。它提供了一组功能和工具,使用户可以定义、创建、查询、更新和管理数据库。常见的DBMS包括MySQL、Oracle、SQL Server、PostgreSQL等。
数据库模型: 数据库模型定义了数据在数据库中的组织方式和关系。常见的数据库模型包括层次模型、网络模型、关系模型和面向对象模型。其中,关系模型是应用最广泛的模型,使用表格的形式来组织数据,并通过关系来描述数据之间的联系。
表格(表): 数据库中的主要组织单位,用于存储数据。每个表由多个列组成,每列代表一个数据字段,而每行则代表一个记录或实体。
查询语言: 用于与数据库进行交互和操作的语言。最常见的查询语言是结构化查询语言(SQL),它允许用户执行诸如查询数据、插入新数据、更新现有数据和删除数据等操作。
数据完整性约束: 用于确保数据库中数据的一致性和准确性的规则。包括主键约束、唯一约束、外键约束、检查约束等。
数据库的主要优势包括:
数据集中化和共享:多个用户可以访问和共享同一个数据库,从而实现数据的集中管理和共享利用。
数据一致性和完整性:通过数据库管理系统强制执行的数据完整性约束,确保数据的一致性和准确性。
数据安全性:数据库提供了各种安全功能,如用户身份验证、权限控制和数据加密,以保护数据的安全性。
数据可恢复性:数据库系统通常提供备份和恢复功能,以防止数据丢失和系统故障。
总的来说,数据库是现代信息系统中不可或缺的基础设施,它为组织和个人提供了高效地管理和利用数据的手段。
2.数据库的语法使用
数据库语法
数据库语法的使用主要涉及到对数据库进行查询、更新、插入和删除等操作。最常见的数据库语言是结构化查询语言(SQL),下面简要介绍一些基本的 SQL 语法用法:
查询数据 (SELECT):
SELECT column1, column2, ...
FROM table_name
WHERE condition;
选择指定表中的列(可以使用通配符 * 选择所有列)
可以使用 WHERE 子句对结果进行筛选,条件可以是等于、大于、小于、模糊匹配等
插入数据 (INSERT):
INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...);
将新行插入到指定表中,为每列指定值
更新数据 (UPDATE):
UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;
更新指定表中符合条件的行的数据
删除数据 (DELETE):
DELETE FROM table_name
WHERE condition;
删除指定表中符合条件的行
创建表 (CREATE TABLE):
CREATE TABLE table_name (column1 datatype,column2 datatype,...
);
创建新表,指定表名和列名及其数据类型
删除表 (DROP TABLE):
DROP TABLE table_name;
删除指定的表及其所有数据
条件运算符:
=: 等于
!= or <>: 不等于
<: 小于
>: 大于
<=: 小于等于
>=: 大于等于
BETWEEN: 在某个范围内
LIKE: 模糊匹配
IN: 在某个值列表中
逻辑运算符:
AND: 与
OR: 或
NOT: 非
数据库的执行过程
数据库的执行过程,大概分为三个步骤:
处理链接
解析与优化
存储引擎
1.处理连接
TCP/IP协议是MySQL客户端和服务器最常用的通信方式。
我们平时所说的MySQL服务器默认监听的端口是3306,这句话的前提是客户端进程和服务器进程使用的是TCP/IP协议进行通信。
我们在使用mysql命令启动客户端程序时,只要在-h参数后跟随IP地址作为服务器进程所在的主机地址,那么通讯方式便是TCP/IP协议。
解析优化
在这个阶段,数据库系统会解析用户提交的 SQL 查询或命令。解析器会检查语法的正确性,并将 SQL 查询转换为内部的数据结构(通常是语法树或查询计划),以便后续的处理。
在解析完成后,数据库系统会对查询进行优化。优化器会考虑多种执行方案,并选择最优的执行计划,以提高查询性能。优化的目标通常是最小化执行时间或者资源消耗。
执行(Execution):
一旦选择了执行计划,数据库系统就会执行实际的查询操作。这包括从磁盘读取数据、执行各种操作(如连接、排序、聚合等)、计算结果,并将结果返回给用户。
在执行过程中,数据库管理系统还可能涉及到锁定和并发控制等机制,以确保数据的一致性和完整性。同时,还会记录执行过程中的日志,以支持事务的回滚和恢复操作。
3.数据库的索引介绍
索引的介绍
数据库索引是一种用于提高数据库查询性能的数据结构。它们类似于书籍的目录,可以加快在表中查找特定数据的速度。索引是在数据库表上创建的数据结构,以便快速地定位和访问表中的特定行。在数据库中,索引通常基于一个或多个列的值。
索引的主要好处包括:
加速数据检索:通过使用索引,数据库可以更快地定位和检索特定行,而无需完全扫描整个表。
提高查询性能:对经常执行的查询创建索引可以显著提高查询性能,特别是对于大型数据集。
提高数据完整性:通过在列上创建唯一索引,可以确保数据的完整性,防止插入重复的值。
索引的缺点:
1、占用存储空间:索引需要额外的存储空间来存储索引数据结构,对于大型表来说可能会占用相当多的空间。
2、增加写操作的成本:在对表进行插入、更新和删除操作时,数据库还需要维护索引,这可能会增加写操作的成本。
3、不当使用可能导致性能下降:如果创建了过多或不必要的索引,可能会导致查询性能下降,因为数据库引擎需要维护大量的索引结构。
常见的索引类型包括:
主键索引:用于唯一标识表中的每一行数据。
唯一索引:确保索引列中的值是唯一的。
复合索引:基于多个列的值创建的索引。
全文索引:用于全文搜索,例如在文本列中搜索特定的单词或短语。
聚集索引和非聚集索引:针对不同的数据库系统,索引的物理存储方式可能不同。
在设计和使用索引时,需要权衡查询性能、存储空间和写操作成本之间的关系,以确保达到最佳的性能和资源利用率。
索引创建注意点:
1.索引应该建在查询应用频繁的字段
2.在用于 where 判断、 order 排序和 join 的(on)字段上创建索引。
3.索引的个数应该适量
4.索引需要占用空间;更新时候也需要维护。
5.区分度低的字段,例如性别,不要建索引。
6.离散度太低的字段,扫描的行数降低的有限。
7.频繁更新的值,不要作为主键或者索引
8.维护索引文件需要成本;还会导致页分裂,IO次数增多。
9.组合索引把散列性高(区分度高)的值放在前面
10.为了满足最左前缀匹配原则 创建组合索引,而不是修改单列索引。组合索引代替多个单列索引(对于单列索引,MySQL基本只能使用一个索引,所以经常使用多个条件查询时更适合使用组合索引)
11.过长的字段,使用前缀索引。当字段值比较长的时候,建立索引会消耗很多的空间,搜索起来也会很慢。我们可以通过截取字段的前面一部分内容建立索引,这个就叫前缀索引。
12.不建议用无序的值(例如身份证、UUID )作为索引
13.当主键具有不确定性,会造成叶子节点频繁分裂,出现磁盘存储的碎片化
索引失效的情况
1.查询条件包含or,可能导致索引失效
2.如果字段类型是字符串,where时一定用引号括起来,否则会因为隐式类型转换,索引失效
3.like通配符可能导致索引失效。
4.联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。
5.在索引列上使用mysql的内置函数,索引失效。
6.对索引列运算(如,+、-、*、/),索引失效。
7.索引字段上使用(!= 或者 < >,not in)时,可能会导致索引失效。
8.索引字段上使用is null, is not null,可能导致索引失效。
9.左连接查询或者右连接查询查询关联的字段编码格式不一样,可能导致索引失效。
10.MySQL优化器估计使用全表扫描要比使用索引快,则不使用索引。
索引不适合哪些场景呢?
数据量比较少的表不适合加索引
更新比较频繁的字段也不适合加索引
离散低的字段不适合加索引(如性别)
索引是不是建的越多越好呢?
当然不是。
索引会占据磁盘空间
索引虽然会提高查询效率,但是会降低更新表的效率。比如每次对表进行增删改操作,MySQL不仅要保存数据,还有保存或者更新对应的索引文件
索引的数据结构
MySQL的默认存储引擎是InnoDB,它采用的是B+树结构的索引。
B+树:只有叶子节点才会存储数据,非叶子节点只存储键值。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。
最外面的方块,的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(粉色所示)和指针(黄色/灰色所示),如根节点磁盘包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、4、5……、65。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表,可以进行范围查询。
为什么要用 B+ 树,而不用普通二叉树?
可以从几个维度去看这个问题,查询是否够快,效率是否稳定,存储数据多少,以及查找磁盘次数。
为什么不用普通二叉树?
普通二叉树存在退化的情况,如果它退化成链表,相当于全表扫描。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。
为什么不用平衡二叉树呢?
读取数据的时候,是从磁盘读到内存。如果树这种数据结构作为索引,那每查找一次数据就需要从磁盘中读取一个节点,也就是一个磁盘块,但是平衡二叉树可是每个节点只存储一个键值和数据的,如果是 B+ 树,可以存储更多的节点数据,树的高度也会降低,因此读取磁盘的次数就降下来啦,查询效率就快。
为什么用 B+ 树而不用 B 树呢?
1.B+相比较B树,有这些优势:
它是 B Tree 的变种,B Tree 能解决的问题,它都能解决。
2.B Tree 解决的两大问题:每个节点存储更多关键字;路数更多
扫库、扫表能力更强
3.如果我们要对表进行全表扫描,只需要遍历叶子节点就可以 了,不需要遍历整棵 B+Tree 拿到所有的数据。
4.B+Tree 的磁盘读写能力相对于 B Tree 来说更强,IO次数更少
5.根节点和枝节点不保存数据区, 所以一个节点可以保存更多的关键字,一次磁盘加载的关键字更多,IO次数更少。
6.排序能力更强
7.因为叶子节点上有下一个数据区的指针,数据形成了链表。
8.效率更加稳定
9.B+Tree 永远是在叶子节点拿到数据,所以 IO 次数是稳定的。
Hash 索引和 B+ 树索引区别是什么?
1.B+ 树可以进行范围查询,Hash 索引不能。
2.B+ 树支持联合索引的最左侧原则,Hash 索引不支持。
3.B+ 树支持 order by 排序,Hash 索引不支持。
4.Hash 索引在等值查询上比 B+ 树效率更高。
5.B+ 树使用 like 进行模糊查询的时候,like 后面(比如 % 开头)的话可以起到优化的作用,Hash 索引根本无法进行模糊查询。
聚簇索引与非聚簇索引的区别?
首先理解聚簇索引不是一种新的索引,而是而是一种数据存储方式。聚簇表示数据行和相邻的键值紧凑地存储在一起。我们熟悉的两种存储引擎——MyISAM采用的是非聚簇索引,InnoDB采用的是聚簇索引。
聚簇索引(Clustered Index)是数据库中一种索引类型,它对表中的数据行进行物理排序,并将数据存储在索引的叶子节点上。与非聚簇索引(Non-clustered Index)不同,聚簇索引实际上改变了表的物理顺序,而非聚簇索引只是提供了一种额外的数据结构来加速数据检索。
在聚簇索引中,索引的键值是按照升序或降序物理排列的。因此,如果表中的数据按照索引键值的顺序进行插入,那么插入的新数据将会被直接放置在磁盘上的适当位置,而不需要进行额外的排序操作。这样的设计使得对于聚簇索引的检索操作非常高效,因为相关的数据通常存储在相邻的物理位置上,从而减少了磁盘访问的成本。
聚簇索引的一个重要特性是每张表只能有一个聚簇索引,因为它实际上定义了表中数据的物理存储顺序。当你在数据库中创建一个聚簇索引时,数据库系统会根据索引键值对表中的数据进行重新组织。
可以这么说:
索引的数据结构是树,聚簇索引的索引和数据存储在一棵树上,树的叶子节点就是数据,非聚簇索引索引和数据不在一棵树上。
一个表中只能拥有一个聚簇索引,而非聚簇索引一个表可以存在多个。
聚簇索引,索引中键值的逻辑顺序决定了表中相应行的物理顺序;索引,索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。
聚簇索引:物理存储按照索引排序;非聚集索引:物理存储不按照索引排序;
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/687d189642c64e648f767b00b28fa6c1.png
聚簇索引的优点包括:
1.提高查询性能:由于相关数据在物理上相邻存储,因此查询通常更快。
2.减少磁盘 I/O 操作:相邻数据存储在一起,减少了磁盘 I/O 操作的次数。
3.覆盖索引:聚簇索引覆盖了整个表,因此可以减少对表的额外访问。
聚簇索引的缺点:
1.插入和更新操作的代价较高:由于数据的物理排序需要维护,插入和更新操作可能会变得更加昂贵。
2.数据页分裂:当插入新数据时,可能需要对数据页进行分裂,这会增加额外的开销。
3.内存消耗:由于相关数据通常存储在一起,因此可能导致缓存不命中的情况,增加了对内存的需求。
4.数据库sql优化
sql性能优化
1.避免使用 select *
很多时候,我们写sql语句时,直接使用select *,一次性查出表中所有列的数据。
select * from user where id=1;
在实际业务场景中,可能我们真正需要使用的只有其中一两列。查了很多数据,但是不用,白白浪费了数据库资源,比如:内存或者cpu。
此外,多查出来的数据,通过网络IO传输的过程中,也会增加数据传输的时间。
还有一个最重要的问题是:select *不会走覆盖索引,会出现大量的回表操作,而从导致查询sql的性能很低。
正例:
select name,age from user where id=1;
sql语句查询时,只查需要用到的列,多余的列根本无需查出来。
2 用union all代替union
我们都知道sql语句使用union关键字后,可以获取排重后的数据。
而如果使用union all关键字,可以获取所有数据,包含重复的数据。
反例:
(select * from user where id=1) union (select * from user where id=2);
排重的过程需要遍历、排序和比较,它更耗时,更消耗cpu资源。
所以如果能用union all的时候,尽量不用union。
正例:
(select * from user where id=1) union all (select * from user where id=2);
除非是有些特殊的场景,比如union all之后,结果集中出现了重复数据,而业务场景中是不允许产生重复数据的,这时可以使用union。
3 小表驱动大表
小表驱动大表,也就是说用小表的数据集驱动大表的数据集。
假如有order和user两张表,其中order表有10000条数据,而user表有100条数据。
这时如果想查一下,所有有效的用户下过的订单列表。
可以使用in关键字实现:
select 字段,字段,字段,字段 from order where user_id in (select id from user where status=1)
也可以使用exists关键字实现:
select 字段,字段,字段,字段 字段,字段,字段,字段 字段,字段,字段,字段 from order
where exists(select 1 from user where order.user_id = user.id and status=1)
前面提到的这种业务场景,使用in关键字去实现业务需求,更加合适。
为什么呢?
因为如果sql语句中包含了in关键字,则它会优先执行in里面的子查询语句,然后再执行in外面的语句。如果in里面的数据量很少,作为条件查询速度更快。
而如果sql语句中包含了exists关键字,它优先执行exists左边的语句(即主查询语句)。然后把它作为条件,去跟右边的语句匹配。如果匹配上,则可以查询出数据。如果匹配不上,数据就被过滤掉了。
这个需求中,order表有10000条数据,而user表有100条数据。order表是大表,user表是小表。如果order表在左边,则用in关键字性能更好。
总结一下:
in 适用于左边大表,右边小表。
exists 适用于左边小表,右边大表。
不管是用in,还是exists关键字,其核心思想都是用小表驱动大表。
4 批量操作
如果你有一批数据经过业务处理之后,需要插入数据,该怎么办?
反例:
for(Order order: list){ orderMapper.insert(order):
}
在循环中逐条插入数据。insert into order(id,code,user_id) values(123,'001',100)
该操作需要多次请求数据库,才能完成这批数据的插入。
但众所周知,我们在代码中,每次远程请求数据库,是会消耗一定性能的。而如果我们的代码需要请求多次数据库,才能完成本次业务功能,势必会消耗更多的性能。
那么如何优化呢?
正例:
orderMapper.insertBatch(List<User> users):
提供一个批量插入数据的方法。
insert into order(id,code,user_id) values(123,'001',100),(124,'002',100),(125,'003',101);
这样只需要远程请求一次数据库,sql性能会得到提升,数据量越多,提升越大。
但需要注意的是,不建议一次批量操作太多的数据,如果数据太多数据库响应也会很慢。批量操作需要把握一个度,建议每批数据尽量控制在500以内。如果数据多于500,则分多批次处理。
5 多用limit
有时候,我们需要查询某些数据中的第一条,比如:查询某个用户下的第一个订单,想看看他第一次的首单时间。
反例:
select id, create_date from order where user_id=123 order by create_date asc;
根据用户id查询订单,按下单时间排序,先查出该用户所有的订单数据,得到一个订单集合。 然后在代码中,获取第一个元素的数据,即首单的数据,就能获取首单时间。
List<Order> list = orderMapper.getOrderList();Order order = list.get(0);
虽说这种做法在功能上没有问题,但它的效率非常不高,需要先查询出所有的数据,有点浪费资源。
那么,如何优化呢?
正例:
select id, create_date from order where user_id=123 order by create_date asc limit 1;
使用limit 1,只返回该用户下单时间最小的那一条数据即可。
此外,在删除或者修改数据时,为了防止误操作,导致删除或修改了不相干的数据,也可以在sql语句最后加上limit。
例如:
update order set status=0,edit_time=now(3) where id>=100 and id<200 limit 100;
这样即使误操作,比如把id搞错了,也不会对太多的数据造成影响。
6 in中值太多
对于批量查询接口,我们通常会使用in关键字过滤出数据。比如:想通过指定的一些id,批量查询出用户信息。
sql语句如下:
select id,name from category where id in (1,2,3…100000000);
如果我们不做任何限制,该查询语句一次性可能会查询出非常多的数据,很容易导致接口超时。
这时该怎么办呢?
select id,name from category where id in (1,2,3…100) limit 500;
可以在sql中对数据用limit做限制。
不过我们更多的是要在业务代码中加限制,伪代码如下:
public List<Category> getCategory(List<Long> ids) { if(CollectionUtils.isEmpty(ids)) return null; if(ids.size() > 500) throw new BusinessException("一次最多允许查询500条记录") return mapper.getCategoryList(ids); }
还有一个方案就是:如果ids超过500条记录,可以分批用多线程去查询数据。每批只查500条记录,最后把查询到的数据汇总到一起返回。
不过这只是一个临时方案,不适合于ids实在太多的场景。因为ids太多,即使能快速查出数据,但如果返回的数据量太大了,网络传输也是非常消耗性能的,接口性能始终好不到哪里去。
7 增量查询
有时候,我们需要通过远程接口查询数据,然后同步到另外一个数据库。
反例:
select 字段,字段,字段,字段 from user;
如果直接获取所有的数据,然后同步过去。这样虽说非常方便,但是带来了一个非常大的问题,就是如果数据很多的话,查询性能会非常差。
这时该怎么办呢?
正例:
select 字段,字段,字段,字段 字段,字段,字段,字段 from user
where id > #{lastId} and create_time >= #{lastCreateTime}
limit 100;
按id和时间升序,每次只同步一批数据,这一批数据只有100条记录。每次同步完成之后,保存这100条数据中最大的id和时间,给同步下一批数据的时候用。
通过这种增量查询的方式,能够提升单次查询的效率。
8 高效的分页
有时候,列表页在查询数据时,为了避免一次性返回过多的数据影响接口性能,我们一般会对查询接口做分页处理。
在mysql中分页一般用的limit关键字:
select id,name,age from user limit 10,20;
如果表中数据量少,用limit关键字做分页,没啥问题。但如果表中数据量很多,用它就会出现性能问题。
比如现在分页参数变成了:
select id,name,age from user limit 1000000,20;
mysql会查到1000020条数据,然后丢弃前面的1000000条,只查后面的20条数据,这个是非常浪费资源的。
那么,这种海量数据该怎么分页呢?
优化sql:
select id,name,age from user where id > 1000000 limit 20;
先找到上次分页最大的id,然后利用id上的索引查询。不过该方案,要求id是连续的,并且有序的。
还能使用between优化分页。
select id,name,age from user where id between 1000000 and 1000020;
需要注意的是between要在唯一索引上分页,不然会出现每页大小不一致的问题。
9 用连接查询代替子查询
mysql中如果需要从两张以上的表中查询出数据的话,一般有两种实现方式:子查询 和 连接查询。
子查询的例子如下:
select 字段,字段,字段,字段 from order
where user_id in
(select id from user where status=1)
子查询语句可以通过in关键字实现,一个查询语句的条件落在另一个select语句的查询结果中。程序先运行在嵌套在最内层的语句,再运行外层的语句。
子查询语句的优点是简单,结构化,如果涉及的表数量不多的话。
但缺点是mysql执行子查询时,需要创建临时表,查询完毕后,需要再删除这些临时表,有一些额外的性能消耗。
这时可以改成连接查询。 具体例子如下:
select o. 字段,字段,字段,字段 from order o
inner join user u on o.user_id = u.id
where u.status=1
10 join的表不宜过多
根据阿里巴巴开发者手册的规定,join表的数量不应该超过3个。
反例:
select a.name,b.name.c.name,d.name from a
inner join b on a.id = b.a_id
inner join c on c.b_id = b.id
inner join d on d.c_id = c.id
inner join e on e.d_id = d.id
inner join f on f.e_id = e.id
inner join g on g.f_id = f.id
如果join太多,mysql在选择索引的时候会非常复杂,很容易选错索引。
并且如果没有命中中,nested loop join 就是分别从两个表读一行数据进行两两对比,复杂度是 n^2。
所以我们应该尽量控制join表的数量。
正例:
select a.name,b.name.c.name,a.d_name from a
inner join b on a.id = b.a_id
inner join c on c.b_id = b.id
如果实现业务场景中需要查询出另外几张表中的数据,可以在a、b、c表中冗余专门的字段,比如:在表a中冗余d_name字段,保存需要查询出的数据。
不过我之前也见过有些ERP系统,并发量不大,但业务比较复杂,需要join十几张表才能查询出数据。
所以join表的数量要根据系统的实际情况决定,不能一概而论,尽量越少越好。
11 join时要注意
我们在涉及到多张表联合查询的时候,一般会使用join关键字。
而join使用最多的是left join和inner join。
left join:求两个表的交集外加左表剩下的数据。
inner join:求两个表交集的数据。
使用inner join的示例如下:
select o.id,o.code,u.name from order o
inner join user u on o.user_id = u.id
where u.status=1;
如果两张表使用inner join关联,mysql会自动选择两张表中的小表,去驱动大表,所以性能上不会有太大的问题。
使用left join的示例如下:
select o.id,o.code,u.name from order o
left join user u on o.user_id = u.id
where u.status=1;
如果两张表使用left join关联,mysql会默认用left join关键字左边的表,去驱动它右边的表。如果左边的表数据很多时,就会出现性能问题。
要特别注意的是在用left join关联查询时,左边要用小表,右边可以用大表。如果能用inner join的地方,尽量少用left join。
12 控制索引的数量
众所周知,索引能够显著的提升查询sql的性能,但索引数量并非越多越好。
因为表中新增数据时,需要同时为它创建索引,而索引是需要额外的存储空间的,而且还会有一定的性能消耗。
阿里巴巴的开发者手册中规定,单表的索引数量应该尽量控制在5个以内,并且单个索引中的字段数不超过5个。
mysql使用的B+树的结构来保存索引的,在insert、update和delete操作时,需要更新B+树索引。如果索引过多,会消耗很多额外的性能。
那么,问题来了,如果表中的索引太多,超过了5个该怎么办?
这个问题要辩证的看,如果你的系统并发量不高,表中的数据量也不多,其实超过5个也可以,只要不要超过太多就行。
但对于一些高并发的系统,请务必遵守单表索引数量不要超过5的限制。
那么,高并发系统如何优化索引数量?
能够建联合索引,就别建单个索引,可以删除无用的单个索引。
将部分查询功能迁移到其他类型的数据库中,比如:Elastic Seach、HBase等,在业务表中只需要建几个关键索引即可。
13 选择合理的字段类型
char表示固定字符串类型,该类型的字段存储空间的固定的,会浪费存储空间。
alter table order add column code char(20) NOT NULL;
varchar表示变长字符串类型,该类型的字段存储空间会根据实际数据的长度调整,不会浪费存储空间。
alter table order add column code varchar(20) NOT NULL;
如果是长度固定的字段,比如用户手机号,一般都是11位的,可以定义成char类型,长度是11字节。
但如果是企业名称字段,假如定义成char类型,就有问题了。
如果长度定义得太长,比如定义成了200字节,而实际企业长度只有50字节,则会浪费150字节的存储空间。
如果长度定义得太短,比如定义成了50字节,但实际企业名称有100字节,就会存储不下,而抛出异常。
所以建议将企业名称改成varchar类型,变长字段存储空间小,可以节省存储空间,而且对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
我们在选择字段类型时,应该遵循这样的原则:
能用数字类型,就不用字符串,因为字符的处理往往比数字要慢。
尽可能使用小的类型,比如:用bit存布尔值,用tinyint存枚举值等。
长度固定的字符串字段,用char类型。
长度可变的字符串字段,用varchar类型。
金额字段用decimal,避免精度丢失问题。
还有很多原则,这里就不一一列举了。
14 提升group by的效率
我们有很多业务场景需要使用group by关键字,它主要的功能是去重和分组。
通常它会跟having一起配合使用,表示分组后再根据一定的条件过滤数据。
反例:
select user_id,user_name from order
group by user_id having user_id <= 200;
这种写法性能不好,它先把所有的订单根据用户id分组之后,再去过滤用户id大于等于200的用户。
分组是一个相对耗时的操作,为什么我们不先缩小数据的范围之后,再分组呢?
正例:
select user_id,user_name from order
where user_id <= 200
group by user_id
使用where条件在分组前,就把多余的数据过滤掉了,这样分组时效率就会更高一些。
其实这是一种思路,不仅限于group by的优化。我们的sql语句在做一些耗时的操作之前,应尽可能缩小数据范围,这样能提升sql整体的性能。
分库分表
分库分表的动机
为什么需要分库呢?
如果业务量剧增,数据库可能会出现性能瓶颈,这时候我们就需要考虑拆分数据库。从这几方面来看:
磁盘存储
业务量剧增,MySQL单机磁盘容量会撑爆,拆成多个数据库,磁盘使用率大大降低。
并发连接支撑
我们知道数据库连接是有限的。在高并发的场景下,大量请求访问数据库,MySQL单机是扛不住的!当前非常火的微服务架构出现,就是为了应对高并发。它把订单、用户、商品等不同模块,拆分成多个应用,并且把单个数据库也拆分成多个不同功能模块的数据库(订单库、用户库、商品库),以分担读写压力。
为什么需要分表?
数据量太大的话,SQL的查询就会变慢。如果一个查询SQL没命中索引,千百万数据量的表可能会拖垮这个数据库。
即使SQL命中了索引,如果表的数据量超过一千万的话,查询也是会明显变慢的。这是因为索引一般是B+树结构,数据千万级别的话,B+树的高度会增高,查询就变慢啦。
InnoDB存储引擎最小储存单元是页,一页大小就是16k。B+树叶子存的是数据,内部节点存的是键值+指针。索引组织表通过非叶子节点的二分查找法以及指针确定数据在哪个页中,进而再去数据页中找到需要的数据,:
假设B+树的高度为2的话,即有一个根结点和若干个叶子结点。这棵B+树的存放总记录数为=根结点指针数单个叶子节点记录行数。
如果一行记录的数据大小为1k,那么单个叶子节点可以存的记录数 =16k/1k =16.
非叶子节点内存放多少指针呢?我们假设主键ID为bigint类型,长度为8字节(面试官问你int类型,一个int就是32位,4字节),而指针大小在InnoDB源码中设置为6字节,所以就是 8+6=14 字节,16k/14B =161024B/14B = 1170
因此,一棵高度为2的B+树,能存放1170 * 16=18720条这样的数据记录。
同理一棵高度为3的B+树,能存放1170 *1170 *16 =21902400,大概可以存放两千万左右的记录。B+树高度一般为1-3层,如果B+到了4层,查询的时候会多查磁盘的次数,SQL就会变慢。
分库分表的策略
1.垂直分表
如果一个单表包含了几十列甚至上百列,管理起来很混乱,每次都select *的话,还占用IO资源。这时候,我们可以将一些不常用的、数据较大或者长度较长的列拆分到另外一张表。
2.水平分表
水平分库是指,将表的数据量切分到不同的数据库服务器上,每个服务器具有相同的库和表,只是表中的数据集合不一样。它可以有效的缓解单机单库的性能瓶颈和压力。
确定分库分表的依据:
1.需要根据业务需求和数据特点确定分库分表的依据。例如,可以根据用户ID、地域、时间等因素进行分库分表。
选择分库分表的策略:
2.水平分库:根据某种规则将数据划分到不同的数据库实例中。常见的分库策略包括哈希取模、范围分片等。
3.水平分表:将同一个数据库中的数据按照某种规则划分到不同的表中。常见的分表策略包括哈希取模、按时间范围等。
创建分库分表规则和映射关系:
4.根据选择的分库分表策略,制定具体的规则和映射关系,以便在程序中根据数据的特征将数据路由到正确的库表上。
数据库表结构设计:
5设计数据库表结构时,需要考虑分库分表的影响。通常需要在表中添加分库分表的字段,并根据分库分表的策略来设计表的主键和索引。
6.数据迁移和初始化:
将现有的数据按照分库分表的规则迁移到新的库表结构中。这可能需要使用数据迁移工具或自定义脚本来完成。
7.查询路由:
设计合适的查询路由逻辑,根据查询条件将查询请求路由到正确的库表上。这可能需要使用中间件或自定义路由逻辑来实现。
8.事务处理:
跨库事务处理可能会比单库事务更加复杂,需要考虑事务的一致性和隔离性。在设计应用程序时,需要特别关注跨库事务的处理。
9.监控和维护:
建立监控系统来监控数据库的性能和健康状况,并定期进行维护和优化,以确保系统的稳定性和可靠性。
10.扩展性考虑:
在设计分库分表方案时,需要考虑系统未来的扩展性需求,如何支持更多的数据和更高的并发访问。