走近科学之《MySQL 的秘密》

走近科学之《MySQL 的秘密》

mysql 存储引擎、索引、执行计划、事务、锁、分库分表、优化


1、存储引擎(storage engines)

存储引擎规定了数据存储时的不同底层实现,如存储机制、索引、锁、事务等。

可以通过 show engines 命令查看当前服务器的存储引擎信息:
show-engines

引擎名称服务器是否支持组件事务分布式事务保存点
InnoDB支持(默认引擎)支持事务、行级锁、外键支持支持支持
MRG_MYISAM支持相同 MyISAM 表的集合不支持不支持不支持
MEMORY支持基于哈希、存储在内存中、对临时表很有用不支持不支持不支持
BLACKHOLE支持写入的任何内容都会消失 ? ? ?不支持不支持不支持
MyISAM支持不支持不支持不支持
CSV支持不支持不支持不支持
ARCHIVE支持不支持不支持不支持
PERFORMANCE_SCHEMA支持不支持不支持不支持
FEDERATED不支持

注:保存点指的是事务过程中的一个逻辑点,用于取消部分事务。当事务结束后,会自动删除该事务的所有保存点。当事务回滚时,可以通过指定保存点使其回退到指定的点。

1.1、InnoDB

InnoDB 存储引擎是一个事务型存储引擎,其特点是支持事务、行锁设计、支持外键、支持非锁定读。

InnoDB 使用 mvcc (多版本并发控制)来获得高并发性;且实现了 SQL 标准的四种隔离级别;使用 next-key locking 的策略来避免幻读现象的产生;提供了 insert buffer(插入缓冲)、double write(二次写)、adaptive hash index(自适应哈希索引)read ahead(预读)等高性能和高可用功能。

对于表中数据的存储,InnoDB 采用了聚集索引的方式,因此每张表中数据的存储都是按主键的存储进行存放(若没有显式创建聚集索引则主键为聚集索引)。若没有显式指定主键,则 InnoDB 会为每行数据生成一个 6 字节的 ROWID(隐藏列)作为主键,且插入数据时自增。

1.2、MRG_MYISAM

MRG_MYISAM 存储引擎实际上时 MyISAM 的组合,它是多个使用 MyISAM 引擎的表的集合。它内部没有数据,真正的数据在 MyISAM 引擎表中。

MRG_MYISAM 解决了在对分库分表数据进行查询时,不确定数据放在那个表里的问题。它将多个表聚合成一个表统一查询,且不会影响原有数据。

1.3、MEMORY

MEMORY 引擎是将表中的数据存放在内存中,若数据库崩溃或重启则表中数据将丢失。它适用于存放临时数据的临时表,以及数据仓库中的维度表。MEMORY 引擎默认使用哈希索引,而不是 B+tree 索引。

虽然 MEMORY 引擎速度非常快,但其在使用上还是有一定的限制。如,只支持表锁,并发性能差;不支持 text 和 blob 类型;存储变长字段字段(varchar)时是按照定长字段(char)的方式进行的,因此会浪费内存。

mysql 使用 MEMORY 引擎作为临时表来存放查询的中间结果集。如果中间结果集大于 MEMORY 引擎表的容量设置,又或者中间结果集含有 text 或 blob 列类型字段,则 mysql 会将其转换到 MyISAM 引擎表存放到而存放到磁盘中。

1.4、BLACKHOLE

黑洞存储引擎,擎如其名。

1.5、MyISAM

MyISAM 存储引擎表级锁、支持全文索引,但不支持事务,主要面向一些 OLAP(联机分析处理)数据库应用。历史上,曾作为 mysql 的默认存储引擎出现过。MyISAM 引擎的缓冲池只缓存索引文件,而不缓存数据,这点不同于大多数存储引擎。

MyISAM 引擎表由 MYD 和 MYI 组成,MYD 用来存放数据文件,MYI 用来存放索引文件。其系统兼容性好,查询速度快。

1.6、CSV

CSV 存储引擎,其特点是不支持索引、不能为 null、不能自增;可对数据文件直接编辑;以 csv 格式存储数据;数据以文本方式存储在文件中;更新和删除先写入到临时文件,然后在 rnd_end() 函数中重新生成数据文件。

CSV 引擎可以将 csv 文件当作 mysql 的表进行处理。其 .frm 为表结构描述,.csv 为数据,.csm 为状态、当前记录数量等。

1.7、ARCHIVE

ARCHIVE 存储引擎只支持 insert 和 select 操作,从 mysql 5.1 开始支持索引。其使用 zlib 算法将数据行进行压缩后存储,压缩比一般达到 1:10。ARCHIVE 非常适合用于存储归档数据,如日志信息。ARCHIVE 引擎使用行锁来实现高并发的插入操作,但其并不是事务安全的存储引擎,其设计目的是提供告诉的插入和压缩功能。

1.8、PERFORMANCE_SCHEMA

PERFORMANCE_SCHEMA 为 mysql 5.5 新出的一个存储引擎,主要用来收集数据库服务器的性能参数。用户无法主动创建该引擎表,都是由 mysql 自己创建。

其提供了进程的锁、文件、互斥变量等信息;保持历史事件的汇总信息,为 mysql 服务器的性能作出详细判断。

1.9、FEDERATED

FEDERATED 存储引擎的表并不存放数据,而是指向一台远程 mysql 服务器上的表。非常类似于 sql server 的链接服务器和 oracle 的透明网关,不同的是,目前 FEDERATED 引擎只支持 mysql 数据库表,不支持异构数据库表。

2、索引(index)

索引是关系型数据库中给数据库表的一列或多列的值排序后的存储结构,其主要目的是为了提高数据库的检索性能。

下文所有概念性全都基于 mysql 数据库 InnoDB 存储引擎。

2.1、索引分类及定义

mysql 索引分类及定义如下:

  • 主键索引:
    是一种特殊的唯一索引,一个表只能有一个主键,且不允许有空值,字段值唯一。一般在建表时创建主键索引。如果没有指定主键,则系统会为每行数据自动生成一个 6 字节的隐藏列 ROWID 作为主键。
  • 唯一索引:
    字段值唯一,允许有空值,若为联合索引,则联合字段的值须唯一。
  • 普通索引:
    最基本的索引,无任何限制。
  • 联合索引:
    在多个字段上创建的索引,此外也无任何限制。
  • 全文索引:
    用于在文章中检索文本信息,尽可在 MyISAM 引擎表中使用。
  • 聚集索引:
    数据行的物理顺序与索引字段值的逻辑顺序一致,一个表中有且只能有一个聚集索引。若不显式指定,则默认为主键索引;若没有主键,则第一个唯一非空索引为聚集索引;若没有唯一非空索引,则自动生成一个 6 字节的隐藏列 ROWID 作为聚集索引。
  • 非聚集索引:
    除却一个聚集索引外,其余都是非聚集索引。

主流的索引数据结构为 B + Tree 和 Hash。聚集索引和非聚集索引都基于 B + Tree 结构。

2.2、B + Tree

B + Tree 为传统意义上的索引,且其是目前关系型数据库中最为常用和最为有效的索引。B + Tree 索引的构造类似于二叉树,根据键值对快速查找数据。

B + Tree 由 B Tree 演化而来, B 树为父,B + 树为子,B 树矮胖,B + 树更矮胖;平衡二叉树算是一种特殊的 B 树(B 树又名多路平衡树);平衡二叉树是基于二分法一种二叉树数据结构。(注:这里的 B 指 banlance,而不是 binary)

B Tree

一个 m 阶 B 树有以下特征:

  • 关键字分布在整棵树中,且每一个出现且只出现在一个节点中(包括根节点、非叶子节点、叶子节点)。
  • 拥有 n 个关键字的节点将把子节点分成 n + 1 段,即拥有 n + 1 个子节点,同时满足查找树的大小关系。
  • 子节点的关键字个数的范围是 [m/2 - 1, m -1],非叶子节点的关键字个数等于子节点数 - 1。
  • 非叶子节点最多只有 m 个子节点,且 m > 2。
  • 根节点的子节点数范围为 [2, m];除根节点外的非叶子节点的子节点数范围为 [m/2, m] 向上取整。

3 阶 B 树

关于上面这个 3 阶 B 树的查询、插入、删除的过程如下:

1、查询元素 4 的过程如下:

  • 获取根节点并与 4 比较,4 < 15,则找到左侧子节点。
  • 获取左侧子节点并与 4 比较,4 < 6 ,则找到左侧子节点。
  • 获取左侧子节点并于 4 比较,4 = 4,返回。

2、插入元素 3 的过程如下:

  • 通过类似查询的方式找到元素 3 应该插入的位置。
  • 但 m 阶 b 树的节点关键字个数范围为 [m/2 - 1, m - 1],即 [1, 2],所以 关键字 2、3、4 所在的节点的中间元素需上移。
  • 父节点插入 元素 3,父节点也超载,继续上移。
  • 上移过程中要始终保持查找树的大小关系。

b-tree 插入元素示意图

3、删除元素 17的过程如下:

  • 通过查询找到 17 元素,然后删除。
  • 删除后 关键字为 20 的节点只有一个子节点了,不满足 b 树条件(非叶子节点最多只有 m 个子节点,且 m > 2),所以需要左旋,20 元素作为子节点,23 元素作为父节点。

b-tree 删除元素示意图

B + Tree

一个 m 阶 B + 树的特征如下:

  • 非叶子节点不存放关键字的指针,只存放关键字及子节点指针,且只进行数据索引。
  • 非叶子节点的关键字个数与子树个数相等。
  • 所有叶子节点中包含了全部的关键字信息,以及这些关键字对应数据的存放的指针,且叶子节点的关键字从小到大顺序排列,左边节点结尾数据会存放右边节点开始数据的指针。
  • 通常 b + 树上会有两个头指针,一个指向根节点,一个指向关键字最小的叶子节点。

b+tree 示意图

关于上述 m 阶 b + tree 的查询、插入、删除的过程如下:

过程?过程和 b 树差不太多。

b + 树相对于 b 树查询时的优点:

  • b + 树磁盘 IO 次数小。每一此获取节点数据都会进行磁盘 IO,由于 b + 树的非叶子节点中不保存数据,只保存索引,所以对于同等大小的磁盘页可以容纳更多的索引,进而降低了磁盘 IO,这也是 b + 树更加矮胖的原因。
  • b + 树查询稳定。由于 b + 树的数据指针只保存在叶子节点,所以每一次查询都必须查找到叶子节点;但 b 树可能刚开始就结束了,又可能得查到末尾。
  • 范围查找时,b + 树只需要遍历叶子节点的链表即可,但 b 树需要通过重复的中序遍历来确定上限。

B + 树索引与哈希索引的区别

b + 树索引:

  • 支持同时匹配多个索引。
  • 支持同时匹配多个索引中的部分索引。
  • 支持索引列左前缀查找。
  • 支持索引列等值、范围查找。

哈希索引:

  • 不支持排序。
  • 不支持范围查找。
  • 不支持联合索引的最左匹配原则。

区别:

  • 在等值查询中,哈希索引占有绝对优势,速度极快;但若存在大量哈希冲突,则哈希等值查找效率极低。
  • 由于哈希索引是无序的,所以哈希索引不支持范围查找;b+树索引的叶子节点是有序链表,有利于范围查找。
  • 由于哈希索引是对索引列的完整值做哈希,所以哈希索引不支持 like ‘aa%’ 模糊查询。
  • 哈希索引不支持联合索引的最左匹配原则,因为对于联合索引是将索引字段值合并后再哈希。
  • b+树索引检索时间较平均;若出现大量哈希冲突,则哈希索引时间波动较大。

b + 树索引适合大多数场景,如组合查询、范围查询、排序、分组、模糊等;hash 索引适合离散性高、数据基数大、等值查询时。

2.3、名词解释
  • 最左匹配原则
    最左匹配原则指的是 where 子句中的条件顺序要与联合索引的列顺序保持一致,否则索引不生效。

    # 给列 a、b、c 建立联合索引 (a, b, c)
    
    # 下列查询索引生效(后五个查询索引生效是因为 mysql 有查询优化器,会自动优化顺序)
    select * from table_name where a = 'a' and b = 'b' and c = 'c'
    select * from table_name where a = 'a' and c = 'c' and b = 'b'
    select * from table_name where b = 'b' and a = 'a' and c = 'c'
    select * from table_name where b = 'b' and c = 'c' and a = 'a'
    select * from table_name where c = 'c' and b = 'b' and a = 'a'
    select * from table_name where c = 'c' and a = 'a' and b = 'b'
    
    # 下列查询索引生效
    select * from table_name where a = 'a'
    select * from table_name where a = 'a' and b = 'b'
    select * from table_name where a = 'a' and b = 'b' and c = 'c'
    
    # 下列查询索引生效(但用到的是 a 列索引,b、c 列都没用到)
    select * from table_name where a = 'a' and c = 'c'
    
    # 下列查询索引不生效
    select * from table_name where b = 'b'
    select * from table_name where c = 'c'
    select * from table_name where b = 'b' and c = 'c'
    select * from table_name where c = 'c' and b = 'b'
    

    顺带说一下最左前缀原则:
    最左前缀原则指的是在模糊查询中,只有 like ‘aa%’ 时索引才会生效。其原因是,对于字符类型,其比较规则是一个字符一个字符比较,只有第一个字符匹配到了,才会比较第二个字符。

    # 索引生效
    select * from table_name where a like 'aa%'
    
    # 索引不生效
    select * from table_name where a like '%aa'
    select * from table_name where a like '%aa%'
    
  • 回表
    如果 select 所需的列包含非索引列或者根据一次索引不能获得所需数据,则需要到对应表中的对应行找到需要的列值,这个过程就叫回表。

    # 如存在表 table_name 含有 id、name、gender、age 字段 且 id 为主键 并为 name 字段建立了普通索引
    
    # 以下查询不需要回表 
    # 因为通过搜索主键索引树就可以确定对应记录的位置
    # 因为主键索引默认为聚集索引,而聚集索引指的是数据行的物理顺序跟字段值的逻辑顺序保持一致
    # 也就是搜索了主键索引就相当于搜索了聚集索引,直接确定了数据行的地址
    select * from table_name where id = 1
    
    # 以下查询需要回表
    # 因为 select 列包含了非索引列,查询时需要先搜索 name 索引树找到主键 id
    # 然后搜索主键索引树找到具体数据行
    select * from table_name where name = 'momo'
    
  • 覆盖索引
    索引覆盖指的是在一颗索引树上就能获取到 select 所需的数据。即无需回表。
    无需回表时,查询速度会更快,所以尽可能的实现索引覆盖将会在很大程度上提高查询效率。
    实现索引覆盖最常见的方式就是为被查询的列建立联合索引。

  • 索引下推
    索引下推是 mysql 在 5.6 版本添加的查询优化策略(ICP 优化)。其具体实现是在索引搜索过程中,对索引列包含的条件字段先做判断,过滤掉不符合条件的数据,以此来减少回表次数。

    # 如存在表 table_name 含有 id、name、gender、age 字段 且 id 为主键 并为 name、gender 字段建立了联合索引
    # 表中存在以下记录
    # (1, 'aa', 1, 21)
    # (2, 'aabb', 1, 21)
    # (3, 'aacc', 2, 23)
    # (4, 'aadd', 2, 23)
    
    # 对于以下查询会出现索引下推
    select * from table_name where name like 'aa%' and gender = 1 and age = 23
    
    # mysql 5.6 之前的版本:
    	# 查询时首先会搜索联合索引树找出 name 以 'aa' 开头的四条记录的 id,然后逐个回表比较 gender 和 age 字段的值
    
    # mysql 5.6 版本:
    	# 查询时首先会搜索索引树,根据最左匹配原则,会先匹配到 name like 'aa%' 的记录,同时过滤掉 gender != 1 的值
    	# 得到 id 为 3、4,然后回两次表再比较 age 的值。
    
  • 索引失效
    以下几种情况会导致索引失效:

    • 全表扫描,会使索引失效。
    • where 子句中使用 !=、<、> 操作符会使索引失效。
    • where 子句中对 null 值判断会使索引失效,如 select * from table_name where name = null。
    • where 子句中使用 or 连接条件会使索引失效。
    • where 子句中使用 in、not in 关键字会使索引失效。
    • where 子句中使用 like ‘%aa’、like ‘%aa%’ 会使索引失效。
    • where 子句中对字段进行表达式操作会使索引失效,如 select * from table_name where age/2 = 20。
    • where 子句中进行函数操作会使索引失效,如 select * from table_name where substring(name, 1, 3) = ‘abc’。
    • where 子句中第一个条件不是联合索引第一个索引列时会使索引失效(最左匹配原则)。
2.4、索引的创建
  • 创建索引可以给检索带来性能上的很大提升(当发现检索慢时首先想到的应该是建立索引)。
  • 首先应该考虑在 where、order by 所涉及到的列上建立索引。
  • 在经常需要检索的字段上建立索引,比如用户名、商品名等。
  • 一个表的索引最好不要超过 6 个,索引固然可以提高检索效率,但同时也降低了 insert 和 update 的效率,因为 insert 或 update 时可能会重建索引。
  • 对于建立了索引的表要尽可能的避免全表扫描,否则会使索引失效。

3、执行计划

通过 mysql 的执行计划可以获取到 sql 执行时表的访问顺序、数据读取操作的操作类型、那些索引可以用、实际使用了那些索引、表之间的引用等。但其也有一定局限性,如不会告诉你触发器、存储过程或用户自定义函数对查询的影响;不能直到 mysql 执行 sql 时对 sql 的自动优化情况;只能作用于 select 操作。

mysql 的执行计划虽然不一定准确,但却在我们分析 sql 时能提供具有很大参考价值的信息。

3.1、explain 说明

可以通过 explain 关键字加 sql 语句来查看执行计划。

explain

其中 id、type、key、rows、Extra 对分析执行情况有很大参考价值。

字段解释如下:

  • id:
    执行 id,表示执行 select 子句或操作表的顺序。
    id 相同,则表示执行顺序从上往下;如果存在子查询,则 id 值递增,值越大,执行优先级越高;id 若相同,则可认为是一组,组内从上往下执行,在所有族中,id 值越大,执行优先级越高。

  • select_type:
    select_type 表示执行的类型。

    select_type描述备注
    SIMPLE简单查询查询中不包含子查询或联合查询
    PRIMARY最外层查询查询中若包含复杂的子查询,则最外层查询被标记为 primary
    SUBQUERY子查询在 select 或 where 列表中包含子查询时,该子查询被标记为 subquery
    DERIVED衍生查询在 from 列表中包含子查询时,该子查询被标记为 derived
    UNION联合查询若第二个 select 出现在 union 关键字之后,则被标记为 union
    UNION RESULT联合结果查询从 union 表中获取结果的 select 被标记为 union result
  • table:
    查询时访问的表名。

  • partitions:
    若查询是基于分区表的,则代表查询时的分区。

  • type:
    type 表示访问类型,又可理解为 mysql 在表中找到所需行的方式。

    type描述备注
    ALL全表扫描扫描全表找到匹配行
    index全索引树扫描扫描全索引树
    range索引树范围扫描常见于 between、<、> 等
    index_merge索引合并使用多个单列索引及逆行搜索
    ref非唯一索引扫描即会通过索引找到一个或多个记录
    eq_ref唯一索引扫描即会通过索引找到一个记录,常见于主键索引扫描或唯一索引扫描
    const常量即 mysql 会将某些查询条件优化且转换为常量,如主键条件
    system特殊常量const 的特殊情况,当表中只有一行记录时为 system
    BULL最高境界mysql 在优化过程中分解语句,执行时甚至不用访问表或索引

    其性能从上到下依次增强,且 range 之前的都可以尝试优化。

  • possible_key:
    查询时可能使用的索引。

  • key:
    查询时实际使用的索引。

  • key_len:
    使用的索引字节长度。

  • ref:
    表示当前查询的连接匹配条件,即那些列或常量被用于查找索引列上的值。

  • rows:
    mysql 根据表统计信息即索引选用信息,预估的找到所需记录需要读取的行数。

  • filtered:
    符合某条件的记录数百分比。

  • Extra:
    额外重要信息。

    Extra描述
    Using index表示当前 select 中使用了覆盖索引
    Using where表示 mysql 存储引擎在接收到记录后进行 “后过滤(Post-filter)”,又可理解为当前 select 没有使用,将使用 where 子句中条件进行过滤
    Using temporary表示当前 select 会使用临时表来存放结果集,常见于排序和分组查询
    Using filesort表示当前排序使用 “文件排序”,无法利用索引排序时就会利用文件排序

4、数据库事务

4.1、事务的 ACID
  • 原子性:
    原子性是指一个事务中的所有操作要么全部成功执行,要么不执行。当其中一个操作发生异常时,事务回滚。
  • 一致性:
    一致性是指当一个事务完成后,使数据从一个状态转换为另一个状态,但是数据的完整性保持一致。
  • 隔离性:
    隔离性是指当多个用户并发访问数据库时,数据库会给每个用户开启事务,以事务为单位访问,事务之间互不影响。
  • 持久性:
    持久性是指事务执行成功后,对数据的修改是持久性的。
4.2、并发事务问题
  • 丢失更新:
    • 第一类丢失是指当两个事务同时修改同一行数据,其中一个事务撤销时会覆盖另一个已完成事务更新的数据。(比如浩浩去银行存100元,这时候银行刚好要扣除年费5元,当扣除成功后,浩浩突然想起晚上越好撸友去网吧通宵,所以不存了。这就会导致账户变为原来的100元)。
    • 第二类丢失是指当两个事务同时更新同一行数据时,其中一个事务的更新结果会被另一个事务覆盖。(比如浩浩跟女友去逛街,浩浩说你去买衣服吧,我在这坐会儿,当女友选好衣服准备用浩浩的银行卡结账时,浩浩突然想起劫出了新皮肤,那一定得买一个,于是两个人同时支付,结果由于事务问题造成银行只扣了皮肤的钱(可把这货乐坏了))。
  • 脏读:
    脏读是指一个事务读取了另一个事务未提交的数据。(比如浩浩的银行卡有100元,好好在深夜加班敲代码,需要一杯咖啡提提神,10元,这时浩浩的女友看中了一个口红,需要97元,两人同时支付,但系统提示浩浩余额不足只有3元了,无奈。但浩浩的女友由于密码错误也支付失败。于是浩浩郁闷,他的女友则纳闷,越想越生气(他竟然偷偷改了支付密码不告诉我)。于是这将会是一个不平静的夜晚。)。
  • 幻读:
    幻读是指一个事务执行两次相同的查询操作,却得到不同的结果。这是因为两次查询执行中间执行了一个更新数据的事务。
  • 不可重复读:
    不可重复读是指一个事务两次查询同一行数据,却得到不同的结果。这是因为两次查询执行中间执行了一个更新数据的事务。

注:

  • 脏读和不可重复度的区别:脏读是一个事务读了另一个事务未提交的数据;不可重复读是一个事务读取了前一个事务已提交的数据。
  • 幻读和不可重复读的区别:幻读是两次查询大量数据;不可重复读是两次查询一行数据。
4.3、事务隔离级别
  • Read_uncommited-只读未提交:
    事务最低级别的隔离,允许一个事务可以看到另一个事务未提交的数据。只能解决第一类丢失更新的问题。
  • Read_commited-读并提交:
    保证了一个事务更新的数据提交后才能被另一个事务看到。解决了第一类更新丢失和脏读的问题。
  • Repeatable_read-重复读:
    保证了同一个事务在相同条件下前后两次能获取到一致的数据。解决了第一类丢失更新、第二类丢失更新、脏读和不可重复读问题。
  • Serilizable-序列化:
    事务串行序列化。解决了脏读、幻读和不可重复读,但效率差,实际开发中一般不用。

5、数据库锁

InnoDB 存储引擎实现了两种标准行级锁:

  • 共享锁(S Lock):允许事务读一行数据。
  • 排它锁(X Lock):允许事务删除或更新一行数据。

6、分库分表

分库分表是程序员三高(高并发、高可用、高性能)中高并发和高性能的一种实现方式或解决方式(它并不能实现高可用)。

6.1、分库分表的背景

对于 “小” 应用(用户量小、数据量小),单机数据库就能够满足;但是对于大型应用(用户量大、数据量大),由于其并发量大、业务数据增长率高、累积的数据量大,数据库的读写速度将会是限制应用性能的最直接原因。

最简单的解决方法就是读写分离,实现方式则是数据库的主从同步。即主库负责写,从库负责读。但随着用户量的增长,随之而来的就是大量用户请求。对于读操作,则可以考虑水平扩展从库;对于写操作来说,由于得保证数据的一致性,主库不能水平扩展。这时候,就需要考虑分库分表。

6.2、分库分表的目的

分库分表的直接目的是解决高并发下的处理速度问题以及数据库服务器的性能问题。

由于每个服务器的 TPS、IO、内存等都是有限的,所以架构设计主要的目的就是在合理利用服务器、网络等资源的前提下尽可能解决高并发问题。当请求并发量大时。可以考虑集群处理,如上文提到的数据库主从同步等;当单库数据量大时,可以考虑切分成更多更小的库(库中的表少,表里的数据少);当单表数据量大时,可以考虑切分成更多更小的表(表的字段少,表里的数据少)。

6.3、分库分表的方法

分库分表一般分为垂直切分和水平切分。当需要分库分表时,可以考虑先垂直后水平,因为垂直切分较水平切分在实现上相对来说较简单且更容易理解。

  • 垂直切分:
    • 垂直分表:
      垂直分表是基于表字段进行的,即对于表字段过多的表,可以考虑将其中不常用、长度较大(如 text 类型)的字段放到明细表中。当一张表字段过百时就可以考虑垂直分表,同时也可以避免数据量大造成的 “跨页问题”。
    • 垂直分库:
      垂直分库是基于系统业务的,即根据不同业务将系统库切分成多个业务库。如电商系统中可以分为用户库、商品库、订单库等。
      垂直分库最重要的一点是业务的拆分,合理的业务拆分有利于具体的实现,且便于后期扩展。同时,分库也意味着增加数据库服务器,以此避免由服务器性能而造成并发请求处理速度慢的问题。
  • 水平切分:
    • 水平分表:
      水平分表是指将数据量过大的单张表根据某种规则拆分成多张表,这种规则可以是时间、地域、业务类型等。如月表、年表、地域表、企业表等。
    • 水平分库:
      水平分库是在水平分表的基础上,添加多个数据库节点,每个库中有系统的所有表,但是表中的数据却是属于同一种规则的。
    • 水平分库分表的切分规则:
      • range:即按照范围来拆分,如 1 ~ 10000 一个表,10001 ~ 20000 一个表。
      • 时间:即按照时间来拆分,如月表、年表等,同样的也是月库、年库。
      • 地域:即按照地理区域来拆分,如西北表、东南表等,同样对应的是西北库、东南库等。
      • 哈希取模:即将数据唯一值的哈希值取模映射到数据库节点哈希值取模上。
6.4、分库分表后的问题

分库分表后也会产生新的问题:

  • 事务:
    分库分表后事务将变成分布式事务。如果依赖数据库本身的事务支持去处理事务,那将会造成高昂的性能代价;如果在程序中处理事务,则会造成代码复杂等问题。
  • 聚合查询:
    如 group by、order by 等。
  • 跨库 join:
    分库分表后关联查询将受到限制,无法 join 位于不同分库的表,也无法 join 分表粒度不同的表,原来只需要一次的查询,现在可能需要多次查询才能完成。可以通过这几种方法解决:全局表:即一些基础数据所有库都存放一份;字段冗余:即将一些冗余信息同时存放在多张表中,以减少关联查询的可能(冗余字段修改时,也就需要修改多张表);程序中组装:即程序中分多次查询,然后再组装。
6.5、关于哈希取模

水平分表的拆分规则中有一种方法是哈希取模,其实现原理是对数据唯一值进行哈希取模,得到的结果将是存放该数据的数据库节点。如分库分表后有三个数据库节点,分别是 1、2、3,当 userId.hashCode()%n == 1 时(n 代表数据库节点个数)则表示该数据对应数据库 2,userId.hashCode()%n == 0 时,则对应数据库 1。

这种方式存在缺陷,即当新增节点或删除节点时,userId.hashCode()%n 将与原来不同,就会造成历史数据全部失效,此时就需要进行数据迁移。于是就有了一致性哈希。

一致性哈希实现原理,首先通过自定义哈希算法求出所有数据库节点的哈希值(如数据库节点命名唯一值的哈希值),使其较均匀的分布在一个 0 ~ 2^32-1 的数字圆环中,然后求出数据唯一值哈希,同样落在圆环上,其对应数据库将是圆环上顺时针方向上最近的一个节点。这样可以减少数据迁移的操作,当某个节点宕掉,只需要迁移这个节点对应的数据即可。

7、优化

数据库方面优化可以从 sql、数据库表结构、系统配置、硬件等这几个点进行。

7.1、SQL 优化
  • 全表扫描,会使索引失效。所以要尽量避免全表扫描。
  • where 子句中使用 !=、<、> 操作符会使索引失效。
  • where 子句中对 null 值判断会使索引失效,如 select * from table_name where name = null。所以对于数据库字段尽量设置默认值,用 name = default_value 来代替 name = null。
  • where 子句中使用 or 连接条件会使索引失效。对于 select age from user where age = 10 or age = 20,可以修改为 select age from user where age = 10 union all select age from user where age = 20。
  • where 子句中使用 in、not in 关键字会使索引失效。可以替换为 between and。
  • where 子句中使用 like ‘%aa’、like ‘%aa%’ 会使索引失效。尽量使用 like ‘aa%’。
  • where 子句中对字段进行表达式操作会使索引失效,如 select * from table_name where age/2 = 20。可替换为 select age from user where age = 40。
  • where 子句中进行函数操作会使索引失效,如 select * from table_name where substring(name, 1, 3) = ‘abc’。可替换为 select age from user where age = 40。
  • where 子句中第一个条件不是联合索引第一个索引列时会使索引失效(最左匹配原则)。
  • 正确使用 exists 与 in。当查询 A 表,子查询 B 表,A 表的数量多于 B 表时可考虑使用 in,反之则使用 exists。
  • 尽量使用数字型字段,也就是字段值只有数字时将其设计为数字型字段,而不是字符型,这样会降低查询和连接的性能。因为系统在查询和连接时对于字符的比较是一个一个的比较,而数字只比较一次。
  • 尽量使用 varchar 代替 char,因为存储空间会直接影响查询效率和性能开销。
  • 任何时候都不要使用 select * ,可使用具体字段代替 *。
  • 尽量避免使用游标,因为游标效率底(极低,大数据量时)。
  • 尽量将多条 sql 语句压缩到一条 sql 中。因为每次 sql 执行前都会进行 网络连接、权限校验等,而这个过程是非常耗时的。
  • 尽量使用 where 代替 having。因为 having 是先检索出所有记录再进行筛选,而 where 是在聚合前就进行筛选。
  • 尽量使用表的别名。当查询需要进行多表连接时,用表的别名,这会减少解析时间。
  • 对于 insert 优化。新建临时表时,如果插入的记录特别多则可考虑使用 select into 代替 create table,效率高且不会产生大量日志,如果数据量小则先 create table,再 insert。
  • 对于 update 优化。如果值 update 一个两个字段则避免 update 全部字段,否则会造成明显的性能消耗,且会产生大量日志。
7.2、数据库表结构优化

数据库表结构优化则指分库分表。

7.3、系统配置优化

系统配置优化指通过修改数据库服务器参数配置来提高其性能。

7.4、硬件优化

买个更牛逼的数据库服务器。

听见奶奶说什么了吗?

奶奶总说…

艾欧尼亚 昂扬不灭!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/183014.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web前端之若依框架图标对照表、node获取文件夹中的文件名,并通过数组返回文件名、在html文件中引入.svg文件、require、icon

MENU 前言效果图htmlJavaScripstylenode获取文件夹中的文件名 前言 需要把若依原有的icon的svg文件拿到哦&#xff01; 注意看生成svg的路径。 效果图 html <div id"idSvg" class"svg_box"></div>JavaScrip let listSvg [404, bug, build, …

TypeScript 学习笔记 第三部分 贪吃蛇游戏

尚硅谷TypeScript教程&#xff08;李立超老师TS新课&#xff09; 1. 创建开发环境 创建工程&#xff0c;使用学习笔记的第二部分安装css部分 npm i -D less less-loader css-loader style-loader对css部分处理&#xff0c;能够运行在低版本浏览器 npm i -D postcss postcss…

【Docker】从零开始:9.Docker命令:Push推送仓库(Docker Hub,阿里云)

【Docker】从零开始&#xff1a;9.Docker命令:Push推送仓库 知识点1.Docker Push有什么作用&#xff1f;2.Docker仓库有哪几种2.1 公有仓库2.2 第三方仓库2.3 私有仓库2.4 搭建私有仓库的方法有哪几种 3.Docker公有仓库与私有仓库的优缺点对比 Docker Push 命令标准语法操作参数…

Design Guidelines for 100 Gbps

文章目录 Stratix V GT Transceiver ChannelsCFP2 Host Connector Assembly and PinoutStratix V GT to CFP2 Interface Layout DesignBoard Stack Up DimensionsExample Design Channel PerformanceSimulation Results for Stratix V GT to CFP2 Connector Layout Design Desi…

【JavaSE】基础笔记 - 异常(Exception)

目录 1、异常的概念和体系结构 1.1、异常的概念 1.2、 异常的体系结构 1.3 异常的分类 2、异常的处理 2.1、防御式编程 2.2、异常的抛出 2.3、异常的捕获 2.3.1、异常声明throws 2.3.2、try-catch捕获并处理 3、自定义异常类 1、异常的概念和体系结构 1.1、异常的…

Mac安装配置typescript及在VSCode上运行ts

一、Mac上安装typescript sudo npm install -g typescript 测试一下&#xff1a;出现Version则证明安装成功 tsc -v 二、在VSCode上运行 新建一个xxx.ts文件&#xff0c;测试能否运行 console.log("helloworld") 运行报错&#xff1a;ts-node: command not…

LabVIEW中如何达到NI SMU最大采样率

LabVIEW中如何达到NI SMU最大采样率 NISMU的数字化仪功能对于捕获SMU详细的瞬态响应特性或表征待测设备&#xff08;DUT&#xff09;响应&#xff08;例如线性调整率和负载调整率&#xff09;至关重要。没有此功能&#xff0c;将需要一个外部示波器。 例如&#xff0c;假设在…

uniapp 轮播图(含组件封装,自动注册全局组件)

效果预览 组件封装 src\components\SUI_Swiper.vue 可参考官网配置更多属性 swipernavigator <script setup lang"ts"> import { ref } from vue defineProps({config: Object, })const activeIndex ref(0) const change: UniHelper.SwiperOnChange (e) &…

关于elementui和ant design vue无法禁止浏览器自动填充问题

以and design vue 为例&#xff1a; 图标用来显隐账号密码 html&#xff1a; <a-form-model-item label"账号密码:" prop"password"><a-input v-if"passwordTab" ref"passwordInput" v-model"form.password" typ…

element中el-switch的v-model自定义值

一、问题 element中的el-switch的值默认都是true或false&#xff0c;但是有些时候后端接口该字段可能是0或者1&#xff0c;如果说再转换一次值&#xff0c;那就有点太费力了。如下所示&#xff1a; <template><el-switchinactive-text"否"active-text&quo…

计算机网络——路由

文章目录 1. 前言&#xff1a;2. 路由基础2.1. 路由的相关概念2.2. 路由的特征2.3. 路由的过程 3 路由协议3.1. 静态路由&#xff1a;3.2. 动态路由&#xff1a;3.2.1. 距离矢量协议3.2.2. OSPF协议&#xff1a;3.2.2.1.OSPF概述OSPF的工作原理路由计算功能特性 3.2.2.2.OSPF报…

Axios 拦截器 请求拦截器 响应拦截器

请求拦截器 相当于一个关卡&#xff0c;如果满足条件就放行请求&#xff0c;不满足就拦截 响应拦截器 在处理结果之前&#xff0c;先对结果进行预处理&#xff0c;比如&#xff1a;对数据进行一下格式化的处理 全局请求拦截器 axios.interceptors.request.use(config > { /…

一起学docker系列之七docker容器卷技术

目录 1 为什么使用容器数据卷&#xff1f;2 数据卷的特点和优势3 使用数据卷的方法3.1 创建容器并挂载数据卷3.2 容器间数据卷的共享与继承 4 数据卷的权限设置5 注意事项5.1 解决权限问题5.2 路径自动创建 结语 对于容器化应用程序的数据管理和持久化&#xff0c;Docker 数据卷…

【2023 云栖】阿里云刘一鸣:Data+AI 时代大数据平台建设的思考与发布

云布道师 本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;刘一鸣 | 阿里云自研大数据产品负责人 演讲主题&#xff1a;DataAI 时代大数据平台应该如何建设 今天分享的主题是 DataAI 时代大数据平台应该如何建设&#xff0…

深度学习技术前沿:探索与挑战

深度学习技术前沿&#xff1a;探索与挑战 一、引言 近年来&#xff0c;深度学习作为人工智能领域的重要分支&#xff0c;取得了令人瞩目的成就。它凭借强大的学习能力和出色的性能&#xff0c;在图像识别、语音识别、自然语言处理等众多任务中展现出巨大潜力。本文将深入探讨深…

后渗透持久性-– 服务控制管理器

执行以下命令将快速检索服务控制管理器实用程序的 SDDL 权限。 sc sdshow scmanager服务控制管理器 – 安全描述符 PowerShell 还可用于枚举所有用户组的 SDDL 权限并将其转换为可读格式。 $SD Get-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\Schedule\S…

vue3 终端实现 (vue3+xterm+websocket)

目录 一、xterm介绍 二、效果展示 三、vue文件实现代码 一、xterm介绍 xterm是一个使用 TypeScript 编写的前端终端组件&#xff0c;可以直接在浏览器中实现一个命令行终端应用&#xff0c;通常与websocket一起使用。 二、效果展示 三、vue文件实现代码 <template>…

芯能转债上市价格预测

芯能转债-113679 基本信息 转债名称&#xff1a;芯能转债&#xff0c;评级&#xff1a;AA-&#xff0c;发行规模&#xff1a;8.8亿元。 正股名称&#xff1a;芯能科技&#xff0c;今日收盘价&#xff1a;12.63元&#xff0c;转股价格&#xff1a;13.1元。 当前转股价值 转债面…

Nginx结合cpolar实现内网穿透多个Windows Web站点端口

文章目录 1. 下载windows版Nginx2. 配置Nginx3. 测试局域网访问4. cpolar内网穿透5. 测试公网访问6. 配置固定二级子域名7. 测试访问公网固定二级子域名 1. 下载windows版Nginx 进入官方网站(http://nginx.org/en/download.html)下载windows版的nginx 下载好后解压进入nginx目…

spark数据倾斜的解决思路

数据倾斜是&#xff1a;多个分区中&#xff0c;某个分区的数据比其他分区的数据多的多 数据倾斜导致的问题&#xff1a; 导致某个spark任务耗时较长&#xff0c;导致整个任务耗时增加&#xff0c;甚至出现OOM运行速度慢&#xff1a;主要发生在shuffle阶段&#xff0c;同样的k…
最新文章