企业级高可用MySQL数据库设计

                               企业级高可用MySQL数据库设计

简介:本资源为博主原创MySQL优化方案,包括MySQL集群搭建(多主,双机热备)、讲解算法演变历程与算法解剖优缺点(时间/空间复杂度、hash开口/封闭寻址、二叉树、AVL平衡二叉树、红黑树、B-树、B+树、B*树)、MySQL引擎、MySQL数据量计算、常见企业级优化策略等。

word文档下载:https://download.csdn.net/download/weixin_38652136/11029000

MySQL CentOS7安装

注意:如果所使用的CentOS7Everything版本自带MySQL v5.7,不需要另行安装,默认账户为root/root,安装前请先检查一下。

  • 先检查系统是否装有mysql

         rpm -qa | grep mysql

  •  下载mysql的repo源

    wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm

    报错: -bash: wget: 未找到命令

    安装插件  yum -y install wget

  • 安装mysql-community-release-el7-5.noarch.rpm包

         sudo rpm -ivh mysql-community-release-el7-5.noarch.rpm

  • 安装MySQL

         sudo yum install mysql-server

  • 重置MySQL密码

       mysql -u root

    报错:

     ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)

    原因:原因是/var/lib/mysql的访问权限问题。

      chown root /var/lib/mysql/

  • 重启MySQL服务

        service mysqld restart

  • 接着登陆设置密码

        mysql -u root

        use mysql;

        update user set password=password('123456') where user='root';

        exit;

接着继续重启MySQL服务

service mysqld restart

  • 接着设置Root账户远程连接密码

mysql -u root -p

GRANT ALL PRIVILEGES ON *.* TO root@"%" IDENTIFIED BY "root"; 

    flush privileges;

重启服务器 service mysqld restart

 

  • 使用外网工具连接MySQL

关闭防火墙

systemctl stop firewalld.service

 

MySQL主从复制与数据备份

主从原理

参照上图可知:

Mater有一个log dump线程,Slave服务器有一个I/O线程和SQL线程。

记录阶段Master会将所有DML SQL语句记录到二进制binlog文件;

请求阶段I/O线程去请求主库的binlog,并将得到的binlog日志写到relay log(中继日志) 文件中;

传输阶段Mater log dump 线程,用来给Slave I/O线程传binlog; (MasterSlave会建立一个长连接进行数据传输)

执行阶段SQL 线程,会读取relay log文件中的日志,并解析成具体操作,来实现主从的操作一致,而最终数据一致。

扩展知识:DML为数据操纵语言(增删改查锁等操作);DDL 数据库定义语言;DCL数据库控制语言 授权,角色控制等 ;TCLTransaction Control Language)事务控制语言 

实现

主服务器:192.168.13.103,从节点:192.168.13.104

主服务器节点

vi /etc/my.cnf  新增以下内容

server_id=103  ###服务器id

log-bin=mysql-bin   ###开启日志文件

重启mysql服务 service mysqld restart

验证是否已经配置成功

show variables like '%server_id%';

能够查询对应配置文件中的server_id 说明已经配置成功

show master status;

能够看到同步的文件,和行数 说明已经配置成功。

创建从服务器连接需要使用的账户

create lming test identified by '123456';

grant all privileges on *.* to 'lming'@'%'identified by '123456' with grant option;

flush privileges;

 

all代表接受所有操作,比如 select,insert,delete....; *.* 代表所有库下面的所有表;% 代表这个用户允许从任何地方登录;为了安全起见,这个%可以替换为你允许的ip地址;

 

从服务器节点

克隆服务器

vi /etc/my.cnf

server_id=104  ###从服务器server_id

log-bin=mysql-bin  ###日志文件同步方式

binlog_do_db=test   ###同步数据库

 

重启mysql服务 service mysqld restart

验证是否已经配置成功

show variables like '%server_id%';

能够查询对应配置文件中的server_id 说明已经配置成功

 

从服务器同步主服务器配置

关闭同步

stop slave;

使用master提供的账户建立同步连接

change master to master_host='192.168.13.103',master_user='lming',master_password='123456',

master_log_file=' mysql-bin.000014',master_log_pos= 824;

master_log_file需要和MasterFile一致,master_log_pos需要与MasterPostiton一致

开始同步

start slave;

检查从服务器复制功能状态

SHOW SLAVE STATUS

 

Slave_IO_Running和Slave_SQL_Running两个线程都为yes表示同步成功。    

重启从服务器mysql:service mysqld restart

验证:在Master上创建表添加数据查看Slave是否会同步数据。

 

异常:

Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs; these UUIDs must be different for replication to work.

原因:

因为服务器克隆的时候交UUID产生了重复。

解决办法:

cat  /etc/my.cnf

cd /var/lib/mysql

rm -rf auto.cnf

重启服务器即可

service mysqld restart

  1. MySQL多主多从集群搭建

架构规划
主master 3307 ---> 从slave 3309 

主master 3308 ---> 从slave 3310

3307 <---> 3308 互为主从

192.168.13.102(主)

192.168.13.103(主)

192.168.13.104(102从)

192.168.13.105(103从)

192.168.13.102 my.cnf添加配置

server_id=102

log-bin=mysql-bin

binlog_do_db=test

###设置步长

auto_increment_increment=2

###设置初始值

auto_increment_offset=1

###开启多主从节点更新同步,如不配置则会出现在master1(102)上进行了更新数据,在master2(103)和slave1(104)上会更新,但是在slave2(105)上不会更新

log-slave-updates

###表示每几次事务提交,MySQL把binlog缓存刷进二进制日志文件中,默认是0,最安全的是设置为1;

sync_binlog=1

192.168.13.103 my.cnf添加配置

server_id=103

log-bin=mysql-bin

binlog_do_db=test

###设置步长

auto_increment_increment=2

###设置初始值

auto_increment_offset=2

###开启多主从节点更新同步,如不配置则会出现在master1(102)上进行了更新数据,在master2(103)和slave1(104)上会更新,但是在slave2(105)上不会更新

log-slave-updates

###表示每几次事务提交,MySQL把binlog缓存刷进二进制日志文件中,默认是0,最安全的是设置为1;

sync_binlog=1

192.168.13.104 my.cnf添加配置

server_id=104

log-bin=mysql-bin

binlog_do_db=test

192.168.13.105 my.cnf添加配置

server_id=105

log-bin=mysql-bin

binlog_do_db=test

(主)创建连接账户

create user lming identified by '123456';

grant all privileges on *.* to 'lming'@'%'identified by '123456' with grant option;

flush privileges;

实现多主多从同步

1. 分别启动四台mysql

2. 在从服务器上停止复制(104、105)

分别执行:

stop slave;

reset slave;

reset master;

3. 在102和103上分别执行如下命令已查看同步文件和position值:

show master status;

5. 在102的两台从服务器上(103、104)执行如下操作实现同步:

stop slave;

change master to master_host='192.168.13.102',master_user='lming',master_password='123456',

master_log_file='mysql-bin.000002',master_log_pos= 4829;

start slave; 

查看从机同步状态:

 

见到Slave_IO_Running和Slave_SQL_Running为yes表示同步成功了。

6. 在103的两台从服务器上(102、105)执行如下操作实现同步:

stop slave;

change master to master_host='192.168.13.103',master_user='lming',master_password='123456',

master_log_file='mysql-bin.000017',master_log_pos= 996;

start slave; 

7. 查看从机同步状态:

8. 测试多主多从同步

经过测试发现在102/103服务器上创建表(DML操作)在其余所有服务器均会同步,多主多从MySQL集群搭建成功。

 

MySQL读写分离

MySQL本身是没有实现读写分离的,想要实现读写分离就必须借助于数据库中间件,主流有两种MyCat和Sharding-JDBC。

什么是MyCat?

      MyCAT是一款由阿里Cobar演变而来的用于支持数据库,读写分离、分表分库的分布式中间件。MyCAT支持Oracle、MSSQL、MYSQL、PG、DB2关系型数据库,同时也支持MongoDB等非关系型数据库。

MyCAT原理MyCAT主要是通过对SQL的拦截,然后经过一定规则的分片解析、路由分析、读写分离分析、缓存分析等,然后将SQL发给后端真实的数据块,并将返回的结果做适当处理返回给客户端。

官方网站:http://www.mycat.io/

基于MyCat实现读写分离

      读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。主数据库进行写操作后,数据及时同步到所读的数据库,尽可能保证读、写数据库的数据一致,比如MySQL的主从复制、Oracle的data guard、SQL Server的复制订阅等。

Linux环境安装MyCat实现读写分离

1、上传安装Mycat-server-1.6.5-release-20180122220033-linux.tar

2、解压安装包tar –zxvf 

3、配置schema.xml 和server.xml

schema.xml:

<?xml version="1.0"?>

<!DOCTYPE mycat:schema SYSTEM "schema.dtd">

<mycat:schema xmlns:mycat="http://io.mycat/">

    <!-- TESTDB1 是mycat的逻辑库名称,链接需要用的 -->

    <schema name="mycat_testdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"></schema>

        <!-- database 是MySQL数据库的库名 -->

    <dataNode name="dn1" dataHost="localhost1" database="test" />

    <!--

    dataNode节点中各属性说明:

    name:指定逻辑数据节点名称;

    dataHost:指定逻辑数据节点物理主机节点名称;

    database:指定物理主机节点上。如果一个节点上有多个库,可使用表达式db$0-99,     表示指定0-99这100个数据库;

 

    dataHost 节点中各属性说明:

        name:物理主机节点名称;

        maxCon:指定物理主机服务最大支持1000个连接;

        minCon:指定物理主机服务最小保持10个连接;

        writeType:指定写入类型;

            0,只在writeHost节点写入;

            1,在所有节点都写入。慎重开启,多节点写入顺序为默认写入根据配置顺序,第一个挂掉切换另一个;

        dbType:指定数据库类型;

        dbDriver:指定数据库驱动;

        balance:指定物理主机服务的负载模式。

            0,不开启读写分离机制;

            1,全部的readHost与stand by writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡;

            2,所有的readHost与writeHost都参与select语句的负载均衡,也就是说,当系统的写操作压力不大的情况下,所有主机都可以承担负载均衡;

-->

    <dataHost name="localhost1" maxCon="1000" minCon="10" balance="3" writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">

        <heartbeat>select user()</heartbeat>

        <!-- 可以配置多个主从 -->

        <writeHost host="hostM1" url="192.168.212.202:3306" user="root" password="root">

            <!-- 可以配置多个从库 -->

            <readHost host="hostS2" url="192.168.212.203:3306" user="root" password="root" />

        </writeHost>

    </dataHost>

</mycat:schema>

server.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- - - Licensed under the Apache License, Version 2.0 (the "License");

         - you may not use this file except in compliance with the License. - You

         may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0

         - - Unless required by applicable law or agreed to in writing, software -

         distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT

         WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the

         License for the specific language governing permissions and - limitations

         under the License. -->

<!DOCTYPE mycat:server SYSTEM "server.dtd">

<mycat:server xmlns:mycat="http://io.mycat/">

  

   <!-- 读写都可用的用户 -->

    <user name="root" defaultAccount="true">

        <property name="password">123456</property>

        <property name="schemas">mycat_testdb</property>

        <!-- 表级 DML 权限设置 -->

        <!--       

        <privileges check="false">

            <schema name="TESTDB" dml="0110" >

                <table name="tb01" dml="0000"></table>

                <table name="tb02" dml="1111"></table>

            </schema>

        </privileges>      

         -->

    </user>

 

    <!-- 只读用户 -->

    <user name="user">

        <property name="password">user</property>

        <property name="schemas">mycat_testdb</property>

        <property name="readOnly">true</property>

    </user>

 

</mycat:server>

 

 

4、客户端连接端口号: 8066

配置文件介绍:

文件

说明

server.xml

Mycat的配置文件,设置账号、参数等

schema.xml

Mycat对应的物理数据库和数据库表的配置

rule.xml

Mycat分片(分库分表)规则

5、进入bin目录

  启动MyCat ./mycat start

  停止MyCat ./mycat stop

6、查看/usr/local/mycat/logs wrapper.log日志 如果是为successfully 则启动成功

关闭防火墙:systemctl stop firewalld.service

只可读的账号      user  user  端口号8066

可读可写的账号    root  123456  端口号8066

数据结构的演变历史与MySQL底层索引数据结构

为什么需要使用索引?

MySQL官方对索引的定义为:索引(Index)是帮助 MySQL 高效获取数据的数据结构。

通俗的讲索引就像书的目录一样可以非常快速的定位到书的页码。

如果向mysql发出一条sql语句请求,查询的字段没有创建索引的话,可能会导致全表扫描,这样的话查询效率非常低。

衡量算法优劣维度

       通常,对于一个给定的算法,衡量器优劣是很有必要的,这有助于我们在架构项目时进行技术选型。衡量算法优劣分为两种方式:

  • 事后统计分析法

事先统计分析法是指的根据算法编写出程序,然后进行大量的测试,对测试数据进行分析,最后得出算法优劣评估报告。

 这种方法虽然可行,但不是一个好的方法。该方法有两个缺陷:一是要想对设计的算法的运行性能进行评测,必须先依据算法编制相应的程序并实际运行;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优势

  • 事先估算分析法

因事后统计分析法更多的依赖于计算机的硬件、软件等环境因素,有时会遮盖算法本身的优劣,因此诞生了事先估算分析法

事先估算分析发是指在编写算法程序前依据一定的方法对算法进行估算。首先一一个用高级语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:

  • 算法采用的策略、方式
  • 编译所产生的代码量
  • 问题输入的规模
  • 机器执行指令的速度

 

已知任何一个算法都是由控制结构(顺序、分支、循环)和原操作(指固有数据类型操作)构成,所以算法时间消耗取决于两者的综合效果,而我们为了比较对于同一个问题使用不同算法的优劣,我们通常的做法是从算法中选取一种对于所研究的问题(或算法类型)来说是基本操作的原操作,以该基本操作的重复执行的次数作为算法的时间量度。

通俗的说就是给定一台机器,其满足执行一条基本语句或算法花费的单位时间一致(硬件、软件),然后限制所使用的控制结构类型,最后研究其在基本操作重复执行的次数来衡量算法的优劣。

时间复杂度

       理解时间复杂度首先需要理解什么是时间频度

      时间频度:

用官方的话来讲一个算法执行所耗费的时间与算法中语句的执行次数成正比,一个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)

通俗的说首先我们知道在事先估算分析法中一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。所以我们就使用了时间频度,时间频度就是我们对于这个时间的一个度量单位

那时间复杂度又是什么呢?

时间复杂度:

在刚才提到的时间频度中,n称为问题的规模,也就是上面所得综合效果,当n不断变化时,时间频度T(n)也会不断变化(正比例)。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度概念。

一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,也就是频度,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时T(n)/f(n)的极限值为不等于零的常数,则称f(n)T(n)的同数量级函数。记作T(n)=(f(n)),称O(f(n)) 为算法的渐进时间复杂度,简称时间复杂度。

上面是官方的解释,T(n)是频度不为零,f(n)函数的到的结果是一个趋近于无穷大nn是问题规模也不等于零因此T(n)/f(n)自然就是不等于零的常数。

通俗的将时间复杂度就是时间频度的规律,通过这个规律我们能在事先衡量一个问题使用某个算法需要多少时间,虽然这个时间不确定,但我们能得到一个粗略的量度

时间复杂度与时间频度的区别就在于时间频度只是记录了某个语句执行的次数,而时间复杂的则是多次执行这个算法后某个语句执行次数的变化规律,其实很像物理中的速度与加速度的区别。

好了通过上面一大段废话终于说清楚时间复杂度是什么了,那么空间复杂度又是什么呢?

空间复杂度:

 类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。渐近空间复杂度也常常简称为空间复杂度。
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。

一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。

存储算法本身所占用的存储空间与算法书写的长短成正比,要压缩这方面的存储空间,就必须编写出较短的算法。

算法的输入输出数据所占用的存储空间是由要解决的问题决定的,是通过参数表由调用函数传递而来的,它不随本算法的不同而改变。

算法在运行过程中临时占用的存储空间随算法的不同而异,有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,我们称这种算法是就地"进行的,是节省存储的算法。有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法就属于这种情况。

  • 如当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时可表示为O(1)
  • 当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为0(10g2n)
  • 当一个算法的空间复杂度n成线性比例关系时,可表示为0(n).

总结:具体地说就是时间复杂度与事件频度有关,而时间频度由与两个综合因素有关(控制结构、原操作),空间复杂度则不同只与三种被占用的空间大小有关(本身空间输入输出数据空间运行时空间),而具体又分为三种情况(如上所述三种)。

 

Hash算法数据结构

       哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

算法:index=Hash(key)

Hash算法演变出了两种数据结构:

  • 开放寻址

开放寻址即每一个值都会存入哈希表对应的一个hash槽中,如果其hash槽已经有值了,官方话语称为“Hash碰撞”,则会插入其相邻的后一个槽位,以此类推。

开放寻址演示网址:https://www.cs.usfca.edu/~galles/visualization/ClosedHash.html

  • 封闭寻址

封闭寻址采用链表法实现,会将相同Hash值对象组成一个链表存放在Hash槽中,与二维数组概念类似,这样当发生“Hash碰撞”会将碰撞的Value插入到Key对应的Hash链表的Hash槽中。Java8以前HashMap采用的就是这种算法,Java8后改用红黑树算法。

封闭寻址演示网址:https://www.cs.usfca.edu/~galles/visualization/OpenHash.html

Hash算法优点:

查找可以直接根据key访问,速度快。

Hash算法缺点:

不能进行范围查找,不实用。

二叉树算法数据结构

由于Hash数据结构并不支持范围查询因此诞生了二叉树数据结构,那么什么是二叉树数据结构呢?

二叉查找树,又称“二叉搜索树”,因为其数据结构图像一颗倒着的树而得名,是具有一下性质的树:

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 任意结点的左、右子树也分别为二叉搜索树。

二叉树演示网址:https://www.cs.usfca.edu/~galles/visualization/BST.html

二叉搜索树优点:

相比于Hash数据结构,不会出现碰撞,并且可以进行范围查询。

二叉搜索树缺点:

  • 相比Hash数据结构时间复杂度提高了,相比于封闭寻址空间复杂度降低了;
  • 依图可知,左右节点并不平衡,造成查询左边数据较快,查询右边数据较慢;
  • 当所有节点按从小到大的顺序依次插入时,结构会变成一个链表。
  • 范围查询会进行“回旋”;

AVL平衡二叉搜索树算法数据结构

平衡二叉查找树,又称 AVL树。 它除了具备二叉查找树的基本特征之外,还具有一个非常重要的特点:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值(平衡因子)不超过1。也就是说AVL树每个节点的平衡因子只可能是-101(左子树高度减去右子树高度)。

 

AVL树演示网址:https://www.cs.usfca.edu/~galles/visualization/AVLtree.html

 

AVL树优点:

相比于二叉搜索树,高度有所下降,不再会出现链表情况,时间复杂度也有降低,算法整体效率更高。

AVL树缺点:

  • 相比Hash数据结构时间复杂度提高了,相比于封闭寻址空间复杂度降低了。
  • 范围查询会进行“回旋”。

红黑树算法数据结构

红黑树(Red-Black Tree)是二叉搜索树(Binary Search Tree)的一种改进。我们知道二叉搜索树在最坏的情况下可能会变成一个链表(当所有节点按从小到大的顺序依次插入后)。而红黑树在每一次插入或删除节点之后都会花Olog N)的时间来对树的结构作修改,以保持树的平衡。也就是说,红黑树的查找方法与二叉搜索树完全一样;插入和删除节点的的方法前半部分节与二叉搜索树完全一样,而后半部分添加了一些修改树的结构的操作。

红黑树基于二叉搜索树,但每个节点除了有key、三个指针:parent(指向父节点)、lchild(指向左节点)、rchild(指向右节点)以外,还多了一个属性colorcolor只能是红色或黑色,并且红黑树除了需要遵循二叉搜索树的三个性质以外,还需要具备如下四个性质:

  • 根节点为黑色
  • 空节点(NIL)是黑色(红黑树中,根节点的parent以及所有叶节点lchildrchild都不指向NULL,而是指向一个定义好的空节点)
  • 任何红色节点的父、左、右节点均为黑色
  • 从根节点都任意一个空节点(NIL)路径上包含的黑色节点数相同

 

提示:按下方地址演示会看到40节点颜色的变化,以及结构的改变。

红黑树演示地址:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

红黑树树优点:

相比于二叉搜索树,高度有所下降,不再会出现链表情况,时间复杂度也有降低,算法整体效率更高。

区别于AVL树的平衡算法,红黑树通过更改结构和颜色来实现平衡,根据统计数据,在性能方面红黑树要比AVL平衡二叉树更加优秀。

红黑树缺点:

  • 相比Hash数据结构时间复杂度提高了,相比于封闭寻址空间复杂度降低了。
  • 范围查询会进行“回旋”。

B-树算法数据结构

维基百科对B树的定义为“在计算机科学中,B树(B-tree)是一种树状数据结构,它能够存储数据、对其进行排序并允许以O(log n)的时间复杂度运行进行查找、顺序读取、插入和删除的数据结构。”

B树,概括来说是一个节点可以拥有多于2个子节点的二叉查找树。与自平衡二叉查找树不同,B-树为系统最优化大块数据的读和写操作。B-tree算法减少定位记录时所经历的中间过程,从而加快存取速度。普遍运用在数据库和文件系统。

通俗的说,B-树算法继承了AVL树特点,并在其基础上进行了扩展,AVL树每一个节点只允许最多拥有2关键字,而B-树允许每个节点最少拥有M/2-1关键字最多拥有M-1关键字,具体满足的性质如下:

  • 任意一个节点最多只能拥有M个子节点,且M>2;
  • 根节点的子节点数为[2,M]
  • 除根结点以外的非叶子结点的儿子数为[M/2, M]
  • 每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
  • 非叶子结点的关键字个数=指向儿子的指针个数-1
  • 非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1]
  • 非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]

注意:实际情况中任何一个节点都不可能永久拥有M个节点,因为一旦某个节点的子节点数达到了M个就会进行分裂,我们称之为“满了”,满了就会分裂出新节点。 但是在未完成分裂前原有节点还是会有片刻时间拥有M个节点的。

具体请参照下图与演示地址进行理解!

 

 

B-数演示地址:https://www.cs.usfca.edu/~galles/visualization/BTree.html

B-树优点:

相比于AVL/红黑树,每一个节点的容量提升,高度大幅度下降,时间复杂度也有降低,并且会大大减少范围查询时的“回旋”的次数,也就减少了IO操作次数。

B-树缺点:

  • 范围查询任然会进行“回旋”,对IO有一定程度的消耗;
  • 节点“满了”时进行分裂,空间复杂度高。

B+树算法数据结构

B+树是B-树的变体,也是一种多路搜索树,除了需要满足B-树的基本性质以外还新增如下性质:

  • 非叶子结点的子树指针与关键字个数相同;
  • 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
  •  为所有叶子结点增加一个链指针;
  • 所有关键字都在叶子结点出现;

以上为官方解释!

新增叶子节点与非叶子节点关系,叶子节点中包含了keyvalue,非叶子节点中只是包含了key,不包含value

所有相邻的叶子节点包含非叶子节点,使用链表进行结合,有一定顺序排序,从而范围查询效率非常高。

演示连接:https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

B+树优点:

  • 相比于AVL/红黑树,每一个节点的容量提升,高度大幅度下降,时间复杂度也有降低。
  • 整个结构的上部分遵循B-树结构,最底层追加链表结构,无需回旋,链表大幅度的提高了范围查询的效率。
  • 非常适合文件索引系统,像MySQL索引底层便是使用的B+树进行的实现,如InnoDBMyISAM

B+树缺点:

节点“满了”时进行分裂,空间复杂度高。

B*树算法数据结构

B-Tree类算法最终进化形态——B*树。

B*树是B+树的变体,在B+树的非根和非叶子节点上再增加指向兄弟节点的指针。

相信理解了B+树的你一定能看懂这幅图,什么?你看不懂?那我再给你详细介绍一下吧。

首先我们来说一下B+树与B*树分裂时不同的处理方式:

B+树的分裂:

当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针。B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针。

B*树的分裂:

当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了)。如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针。

根据以上B+树与B*树分裂的对比可看出,B+树一旦节点“满了”就会分裂新节点存储关键字,原节点分配规则为(M*1/2),而B*树会具情况而定,且原节点与其兄弟节点分配规则为(M*1/3),B+树分配规则,没有合理的利用空间,而是直接创建新的空间,导致不论是原节点空闲空间还是新节点空闲空间都很大,而B*树利用了其兄弟节点空闲空间,使得空间复杂度下降。

B+树优点:

  • 具备B+树优点。
  • 基于B+树算法,新增兄弟节点指针,降低了空间复杂度。

B+树缺点:

兄弟节点“满了”时原节点任然会进行分裂,相比较于AVL树、红黑树空间复杂度高。

MySQL的MyISAM引擎与InnoDB引擎的区别?

主键索引: MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。这里设表一共有三列,假设我们以Col1为主键,如下图MyISAM表的主索引(Primary key)示意:

 

InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同.

MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

我们知道MySQL底层是使用的B+树结构,那又为什么使用B+树呢?

为什么MySQL底层使用b+树?

B+树相比B树,新增叶子节点与非叶子节点关系,叶子节点中包含了keyvalue,非叶子节点中只是包含了key,不包含value

所有相邻的叶子节点包含非叶子节点,使用链表进行结合,有一定顺序排序,从而范围查询效率非常高

注意:MyISAMInnoDBB-Tree索引不同的实现方式
MyISAM底层使用B+ 叶子节点的value对应存放行数的地址,在通过行数定位到数据。

InnoDB底层使用B+树,叶子节点的value对应存放是行的data数据,相比MyISAM效率要高一些,但是比较占硬盘内存大小。

MySQL B+树能够存放多少字节数据?

局部性原理与磁盘预读

计算机科学中著名的局部性原理当一个数据被用到时, 其附近的数据也通常会马上被使用。
    为了提高效率,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。

这里的一定长度叫做页,也就是操作系统操作磁盘时的基本单位。一般操作系统中.页的大小是4Kb (getconf  PAGE_SIZE)

所以如果在磁盘中读取1kb,实际会读取4kb

假设在B+树一个节点为1

如果从磁盘读取超过1页大小,根据局部性原理与磁盘预读 会读出2页大小
如果从磁盘读取小于1页大小,根据局部性原理与磁盘预读 会读出1页大小

根据以上规则,如果读取正好是页的倍数,这样就可以不用浪费,所以B+树的每一个节点是页的倍数是最佳的。

MySQL中我们的InnoDB页的大小默认是16k,当然也可以通过参数设置:

show variables like 'innodb_page_size';

16384/1024=16kb;

回到问题上,MySQLb+树能够存放多少字节数据?

假设一行为1kb,那么一页可以读取16行数据,一个叶子节点可以存放16条数据

那么非叶子节点存放多少条数据?非叶子节点存放索引值(bigint 8b)和指针(6b)

那么一页16*1024/(8+6)=1170指针

B+ 高度为2  1170*16=18720 条数据

B+ 高度为3  1170*1170*16=21902400 条数据

所以在InnoDB中B+树高度一般为1-3,它就能满足千万级的数据存储。在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。

MySQL数据库优化方案

Mysql的优化,大体可以分为三部分:索引的优化sql慢查询的优化表的优化

开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。

                先捕获低效SQL→慢查询优化方案→慢查询优化原则

MySQL数据库配置慢查询

参数说明:

slow_query_log 慢查询开启状态

slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录)

 

long_query_time 查询超过多少秒才记录

 

1.查询慢查询配置

show variables like 'slow_query%';

2.查询慢查询限制时间

show variables like 'long_query_time';

3.将 slow_query_log 全局变量设置为“ON”状态

set global slow_query_log='ON';

4.查询超过1秒就记录

set global long_query_time=1;

5.查询cat /var/lib/mysql/localhost-slow.log

service mysqld restart

 

explain是MySQL提供给开发者分析SQL的关键字,通过EXPLAIN我们可以查看到SQL是否使用了索引,操作的数据行数等等信息。

数据库优化开启慢查询是每个程序员都必须懂得要领!

索引为什么会失效?注意那些事项?

1.索引无法存储null值

2.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)

要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引

3.对于多列索引,不是使用的第一部分,则不会使用索引

4.like查询以%开头

5.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引

6.如果mysql估计使用全表扫描要比使用索引快,则不使用索引

联合索引为什么需要遵循左前缀原则?

如果在一张表中,存在联合索引的话,在根据条件查询的时候必须要加上第一个索引条件。

EXPLAIN select * from user_details WHERE id=1 and user_name=‘zhangsan’;--索引生效

EXPLAIN select * from user_details WHERE user_name=‘zhangsan’;--索引是不生效的

因为索引底层采用B+树叶子节点顺序排列,必须通过左前缀索引才能定位到具体的节点范围。

表分库为什么能够提高数据库查询效率?

因为将一张表的数据拆分成多个n张表进行存放,让后再使用第三方中间件(MyCat或者Sharding-JDBC)并行同时查询,让后在交给第三方中间进行组合返回给客户端。

 

发布了17 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_38652136/article/details/88616739