mysql优化之(索引为什么会选择B+树存储及平衡二叉树插入实现)

说明:mysql底层使用的时B+树,mysql索引是放在磁盘上面的,因此每次读取索引时通过IO从磁盘读取。

1、hash索引:无规则、不能排序

2、二叉树:解决hash索引不能排序问题,但是当数据有序时会出现线性排列,树的深度会变得很深,会消耗大量IO。

3、平衡二叉树:解决二叉树数据有序时出现的线性插入树太深问题,树的深度会明显降低,极大提高性能,但是当数据量很大时,一般mysql中一张表达到3-5百万条数据是很普遍的,因此平衡二叉树的深度还是非常大,mysql读取时还是会消耗大量IO,不仅如此,计算从磁盘读取数据时以页(4KB)为单位的,及每次读取4096byte。平衡二叉树每个节点只保存了一个关键字(如int即4byte),浪费了4092byte,极大的浪费了读取空间。

4、B-树:解决平衡二叉树树深度的问题,解决了平衡二叉树读取消耗大量内存空间的问题。因为B-树每个节点可以存放多个关键字,最大限度的利用了从磁盘读取的内存空间,单节点存放多个关键字同时也大大减少了树的深度。极大的提高了mysql的查询性能。但是B-树还是有缺点,B-树对有范围查找的查询(如age>20)时采用的还是中序排序法,因此也需要多遍历,并且查询性能不稳定,比如查询(select * from table where id = 222 和 select * from table where id = 223)时在查询效率(耗时)上可能会存在一定的差别,因为B-树还是将关键字,这里为id,存放在根节点和叶节点的,如果运气好,可能id=222这个关键字就在第一个节点,消耗一次IO就找到了,而id=223可能在叶节点,需要消耗3次IO才能找到。因此B-树对同一条sql语句的查询性能可能会有很大影响(确实感觉有点扯,但是事实时这样)。

5、B+树:将关键字全部存放在叶子节点(查询更稳定,同一条mysql语句执行效率时相同的,都会消耗3次IO),将相邻叶子节点的地址保存起来(相比于B-树,对于mysql的范围查找不用再使用中序查找,而是可以直接快读获取到。)

总结:mysql最终使用B+树为mysql索引的底层实现,mysql维护索引有很大的消耗的,会极大的影响数据库的增、改、删操作,因此建立有用的索引很重要,索引字段所占字节越少,mysql一次读取的关键字也会也多,消耗IO会更少、命中率会更大,查询效率随之也会更高。

B+树是B-树的变种(PLUS版)多路绝对平衡查找数,他拥有B-树的优势。
B+树扫库、表能力更强(因为B+树只在叶子节点保存数据了,因此每次IO读取的数据会更多。)
B+树的磁盘读写能力更强(因为B+树只在叶子节点保存数据了,因此每次IO读取的数据会更多。)
B+树的排序能力更强。(因为叶子节点添加了左边最大的指向右边最小的,有天然的排序。)
B+树的查询效率更加稳定。(B-树可能一次IO就命中查询,但是同一类查询,不同的值可能一次IO就命中、可能3次IO才命中,查询效率不稳定。而B+树IO消耗次数是固定的,因此叶子节点才保存数据地址,更加稳定。)

下面是我对平衡二叉树的插入实现代码(效率不高,不过最终花了大概一周时间还是实现了),这也说明了mysql维护索引所带来的消耗,B+树实现起来会更难,更复杂,因此维护成本会更高。:

插入实现关键点:

1.插入新节点时  节点左<根节点<节点右

2.计算每个节点的高度以及平衡因子

3.知道使树不平衡的4种类型以及旋转方式(RR、LL、RL、LR)

package com.kf.tree;


import org.apache.commons.lang3.StringUtils;

public class AVL {
    private Node root = null;//根节点
    private Node newNode = null;//最新节点
    private boolean isBanlance = true;
    private int count = 0;//树节点个数

    public static void main(String[] args) {
        //创建一颗树
        AVL tree = new AVL();
        //插入节点
//        Integer[] arr = {3,2,1,4,5,6,7,10,9,8};
        Integer[] arr = {30,20,11,40,50,60,70,100,90,80,3,2,1,4,5,6,7,10,9,8};
        for(int i = 0 ;i < arr.length ;i++) {
            Node newNode = new Node();
            newNode.setValue(arr[i]);
            newNode.setOldBf(0);
            newNode.setBf(0);

            tree.insertNode(newNode);
            tree.setNewNode(newNode);
            //计算该树的平衡因子(oldBf是平衡的,bf是当前的,也就可能是不平衡的)
            tree.calcBf();
            //插入后查看树是否平衡
            tree.setBanlance(true);
            tree.isBanlance(tree.getRoot());
            if(!tree.isBanlance()) {
                System.out.println("插入"+newNode.getValue()+"后该树不平衡");
                //不平衡则调整
                tree.banlance();
                //重新计算平衡因子
                tree.calcBf();
            }else{
                System.out.println("该树平衡");
            }
            int val = arr[i];
            System.out.println("插入"+val+"后");
            System.out.println(tree);

        }
        System.out.println(tree);
    }

    //将树调整平衡
    private void banlance() {
        //调整思路-共四种
        String type = getType(newNode);
        //1.确认是那种类型 LL、RR、RL、LR型
        switch (type) {
            case "LL" : R_rotate(finder);
                break;
            case "RR" : L_rotate(finder);
                break;
            case "LR" : L_rotate(finder.getLeft());R_rotate(finder);
                break;
            case "RL" : R_rotate(finder.getRight());L_rotate(finder);
                break;
            default:
                System.out.println("类型不存在type:"+type);
        }
    }

    //绕node右旋
    private void R_rotate(Node node) {
        Node top = node.getParent();
        String topDir = "";
        if(top != null) {
            topDir = node.getParent().getLeft() == node ? "left" : "right";
        }
        Node left = node.getLeft();
        Node change = left.getRight();

        //fixme 断开
        //change断开
        if(change != null) {
            change.setParent(null);
        }
        //left断开
        left.setRight(null);
        left.setParent(null);
        //node断开
        node.setLeft(null);
        node.setParent(null);
        //top断开
        if(StringUtils.equals("left",topDir)) {
            top.setLeft(null);
        }else if(StringUtils.equals("right",topDir)) {
            top.setRight(null);
        }

        //fixme 连接
        //change连接
        if(change != null) {
            change.setParent(node);
        }
        //node连接
        node.setLeft(change);
        node.setParent(left);
        //left连接
        left.setRight(node);
        if(!StringUtils.equals("",topDir)) {
            left.setParent(top);
        }
        //top连接
        if(StringUtils.equals("left",topDir)) {
            top.setLeft(left);
        }else if(StringUtils.equals("right",topDir)) {
            top.setRight(left);
        }else{
            root = left;
        }


    }

    //绕node左旋
    private void L_rotate(Node node) {
        Node top = node.getParent();
        String topDir = "";
        if(top != null) {
            topDir = node.getParent().getLeft() == node ? "left" : "right";
        }
        Node right = node.getRight();
        Node change = right.getLeft();

        //fixme 断开
        //change断开
        if(change != null) {
            change.setParent(null);
        }
        //right断开
        right.setLeft(null);
        right.setParent(null);
        //node断开
        node.setRight(null);
        node.setParent(null);
        //top断开
        if(StringUtils.equals("left",topDir)) {
            top.setLeft(null);
        }else if(StringUtils.equals("right",topDir)) {
            top.setRight(null);
        }

        //fixme 连接
        //change连接
        if(change != null) {
            change.setParent(node);
        }
        //node连接
        node.setRight(change);
        node.setParent(right);
        //right连接
        right.setLeft(node);
        if(!StringUtils.equals("",topDir)) {
            right.setParent(top);
        }
        //top连接
        if(StringUtils.equals("left",topDir)) {
            top.setLeft(right);
        }else if(StringUtils.equals("right",topDir)) {
            top.setRight(right);
        }else{
            root = right;
        }
    }


    private Node finder;

    //获取该不平衡树属于哪一种类型,并把发现者找出来
    private String getType(Node node) {
        if(Math.abs(node.getParent().getBf()) > 1) {
            String firstType = "";
            String lastType = "";
            finder = node.getParent();
            if(node == finder.getLeft()) {
                firstType = "L";
            }else{
                firstType = "R";
            }
            if(newNode.getValue() < node.getValue()) {
                lastType = "L";
            }else{
                lastType = "R";
            }
            return firstType+lastType;
        }else{
            return getType(node.getParent());
        }
    }

    //返回树是否平衡
    private void isBanlance(Node node) {
        if(Math.abs(node.getBf()) > 1) {
            isBanlance =  false;
        }else{
            if(node.getLeft() != null) {
                isBanlance(node.getLeft());
            }
            if(node.getRight() != null) {
                isBanlance(node.getRight());
            }
        }
    }

    //计算该树各节点的平衡因子
    private void calcBf() {
        //遍历到的当前节点
        Node currNode = root;
        //计算某个节点
        calcNodeBf(currNode);
    }

    private void calcNodeBf(Node currNode) {
        if(currNode == null) {
            return;
        }
        int lh = high(currNode.getLeft());
        int rh = high(currNode.getRight());
        currNode.setOldBf(currNode.getBf());
        currNode.setBf(lh-rh);
        //计算孩子的平衡因子
        calcNodeBf(currNode.getLeft());
        calcNodeBf(currNode.getRight());
    }

    //树高度
    private Integer high(Node node) {
        Integer lh = 0;
        Integer rh = 0;
        if(node == null) {
            return 0;
        }else if(node.getLeft() == null && node.getRight() == null) {
            return 1;
        }else {
            lh += high(node.getLeft());
            rh += high(node.getRight());
            return  (lh > rh ? lh : rh)+1;
        }
    }

    //插入节点
    private void insertNode(Node newNode) {
        //为空树-设为根节点
        if(root == null) {
            count++;
            root = newNode;
            return;
        }
        //不为空树-大小比较,放置该节点
        handleNode(root,newNode);
    }

    private void handleNode(Node currNode, Node newNode) {
        //1.小于当前节点
        if(newNode.getValue() < currNode.getValue()) {
            if(currNode.getLeft() == null) {
                count++;
                //当前节点的左边放置newNode
                newNode.setParent(currNode);
                currNode.setLeft(newNode);
            }else{
                handleNode(currNode.getLeft(),newNode);
            }
        }else if(newNode.getValue() > currNode.getValue()){
            //2.大于当前节点
            if(currNode.getRight() == null) {
                count++;
                //当前节点的右边放置newNode
                newNode.setParent(currNode);
                currNode.setRight(newNode);
            }else{
                handleNode(currNode.getRight(),newNode);
            }
        }else{
            System.out.println("该树存在相等的值,暂不做处理。:"+newNode.getValue());
        }


    }

    public Node getRoot() {
        return root;
    }

    public void setRoot(Node root) {
        this.root = root;
    }

    public boolean isBanlance() {
        return isBanlance;
    }

    public void setBanlance(boolean banlance) {
        isBanlance = banlance;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Node getNewNode() {
        return newNode;
    }

    public void setNewNode(Node newNode) {
        this.newNode = newNode;
    }
}

Node类:

package com.kf.tree;

public class Node {
    private Node parent;//节点的直接父亲
    private Node left;//左孩子
    private Node right;//右孩子
    private Integer oldBf;//插入新元素前的平衡因子
    private Integer bf;//插入新元素后的平衡因子
    private int value;//值

    public Integer getOldBf() {
        return oldBf;
    }

    public void setOldBf(Integer oldBf) {
        this.oldBf = oldBf;
    }

    public Node getParent() {
        return parent;
    }

    public void setParent(Node parent) {
        this.parent = parent;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    public Integer getBf() {
        return bf;
    }

    public void setBf(Integer bf) {
        this.bf = bf;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

猜你喜欢

转载自blog.csdn.net/Lei_Da_Gou/article/details/89741555