MySQL中B+树索引

B+树索引

为什么 MySQL 会使用 B+树作为索引结构?

1)高效的查找性能

B+树是一种自平衡树,每个叶子节点到根节点的路径长度相同,B+ 树在插入和删除节点时会进行分裂和并操作,以保证树的平衡,但它又会有一定的冗余节点。

查找、插入、删除等操作的时间复杂度为 O(log n),能保证在大数据量情况下也能有较快的响应时间。

2)树的高度增长不会过快,使得查询磁盘的 I/O 次数减少

B+树不像红黑树,数据越多树的高度增长就越快。它是多叉树,非叶子节点仅保存主键或索引值和页面指针,使得每一页能容纳更多的记录,因此内存中就能存放更多索引,容易命中缓存,使得查询磁盘的 I/O 次数减少。

3)范围查询能力强

B+树特别适合范围查询。因为叶子节点通过链表链接,从根节点定位到叶子查找到范围的起点之后,只需要顺序扫描链表即可遍历后续的数据,非常高效。

 

详细描述 MySQL 的 B+树中查询数据的全过程。

1)数据从根节点找起,根据比较数据键值与节点存储的索引键值,确定数据落在哪个区间,从而确定分支,从上到下最终定位到叶子节点

2)叶子节点存储实际的数据行记录,但是一页有 16KB 大小,存储的数据行不止一条

3)叶子节点中数据行以组的形式划分,利用页目录结构,通过二分查找可以定位到对应的组

4)定位组后,利用链表遍历就可以找到对应的数据行

MySQL 三层 B+ 树能存多少数据?

参数:

  • 每个节点页大小为 16KB(即16384字节)
  • 假设每个数据记录的主键和数据大小为 1 KB
  • 每个内部节点(非叶子节点)存储的是指向节点的指针和索引键

三层 B+ 树的存储计算:

  • 叶子节点:第三层为叶子节点,每个叶子节点页可以存储 16条数据记录
  • 第二层(中间层):假设每个指针 6字节 和索引键的大小为8字节,那么每个中间节点页可以执行 1170 个叶子节点。(16*1024/(6+8))
  • 第一层(根节点):根节点可以指向 1170 个中间节点。

因此,三层 B+ 树大致能存储的数据总量为:1170 * 1170 * 16 = 21902400,一颗三层的 B+ 树在 MySQL 中可以存储大约 2000万条记录。

 B+树 Java 实现:
/**
 * B+树节点抽象类
 */
abstract class BPlusNode {
    List<Integer> keys = new ArrayList<>(); // 存储键值

    // 抽象方法定义
    abstract Object get(int key);           // 查询方法
    abstract void insert(int key, Object value); // 插入方法
    abstract boolean isOverflow(int order); // 判断是否溢出
    abstract SplitResult split();           // 分裂节点
    abstract int getFirstKey();             // 获取第一个键(用于分裂后提升)
    abstract boolean isLeaf();              // 判断是否为叶子节点
}
/**
 * 内部节点类(非叶子节点)
 */
class InternalNode extends BPlusNode {
    List<BPlusNode> children = new ArrayList<>(); // 子节点指针

    @Override
    Object get(int key) {
        // 根据键值找到合适的子节点
        int i = 0;
        while (i < keys.size() && key >= keys.get(i)) i++;
        return children.get(i).get(key); // 递归查询
    }

    @Override
    void insert(int key, Object value) {
        // 路由到合适的子节点
        int i = 0;
        while (i < keys.size() && key >= keys.get(i)) i++;
        children.get(i).insert(key, value);
    }

    @Override
    boolean isOverflow(int order) {
        // 内部节点最多允许order个子节点
        return children.size() > order;
    }

    @Override
    SplitResult split() {
        // 分裂内部节点(提升中间键)
        int mid = keys.size() / 2;
        int promoteKey = keys.get(mid); // 中间键提升到父节点

        // 创建右兄弟节点
        InternalNode right = new InternalNode();
        right.keys.addAll(keys.subList(mid + 1, keys.size()));
        right.children.addAll(children.subList(mid + 1, children.size()));

        // 清理已移动的数据
        keys.subList(mid, keys.size()).clear();
        children.subList(mid + 1, children.size()).clear();

        return new SplitResult(promoteKey, this, right);
    }

    @Override
    int getFirstKey() {
        // 内部节点的第一个键是其最左子节点的第一个键
        return children.get(0).getFirstKey();
    }

    @Override
    boolean isLeaf() {
        return false;
    }
}
/**
 * 叶子节点类
 */
class LeafNode extends BPlusNode {
    List<Object> values = new ArrayList<>(); // 存储实际数据
    LeafNode next;                          // 指向下一个叶子节点(形成链表)

    @Override
    Object get(int key) {
        // 在叶子节点中直接查找数据
        int idx = keys.indexOf(key);
        return idx != -1 ? values.get(idx) : null;
    }

    @Override
    void insert(int key, Object value) {
        // 保持有序插入
        int i = 0;
        while (i < keys.size() && keys.get(i) < key) i++;
        keys.add(i, key);
        values.add(i, value);
    }

    @Override
    boolean isOverflow(int order) {
        // 叶子节点最多允许order-1个键
        return keys.size() > order - 1;
    }

    @Override
    SplitResult split() {
        // 分裂叶子节点(右兄弟的第一个键需要提升)
        LeafNode right = new LeafNode();
        int mid = keys.size() / 2;

        // 移动后半部分数据到右兄弟
        right.keys.addAll(keys.subList(mid, keys.size()));
        right.values.addAll(values.subList(mid, values.size()));
        keys.subList(mid, keys.size()).clear();
        values.subList(mid, values.size()).clear();

        // 维护叶子链表
        right.next = next;
        next = right;

        // 返回分裂结果(右兄弟的第一个键需要提升)
        return new SplitResult(right.keys.get(0), this, right);
    }

    @Override
    int getFirstKey() {
        return keys.get(0);
    }

    @Override
    boolean isLeaf() {
        return true;
    }
}
/**
 * 分裂结果包装类
 */
class SplitResult {
    int key;         // 需要提升到父节点的键
    BPlusNode left;  // 分裂后的左节点
    BPlusNode right; // 分裂后的右节点

    SplitResult(int key, BPlusNode left, BPlusNode right) {
        this.key = key;
        this.left = left;
        this.right = right;
    }
}
/**
 * B+树主类
 */
public class BPlusTree {
    private BPlusNode root; // 根节点
    private LeafNode head;  // 叶子链表头节点(用于范围查询)
    private final int order; // 树阶数

    public BPlusTree(int order) {
        this.order = order;
        root = new LeafNode();
        head = (LeafNode) root;
    }

    /**
     * 插入键值对
     */
    public void insert(int key, Object value) {
        // 1. 查找插入路径
        List<BPlusNode> path = new ArrayList<>();
        BPlusNode node = root;
        while (!node.isLeaf()) {
            InternalNode internal = (InternalNode) node;
            path.add(node);
            int i = 0;
            while (i < internal.keys.size() && key >= internal.keys.get(i)) i++;
            node = internal.children.get(i);
        }

        // 2. 插入叶子节点
        LeafNode leaf = (LeafNode) node;
        leaf.insert(key, value);

        // 3. 处理节点分裂(从下往上)
        if (leaf.isOverflow(order)) {
            SplitResult result = leaf.split();
            if (path.isEmpty()) {
                // 根节点分裂需要创建新根
                InternalNode newRoot = new InternalNode();
                newRoot.keys.add(result.key);
                newRoot.children.add(result.left);
                newRoot.children.add(result.right);
                root = newRoot;
            } else {
                insertIntoParent(path, result);
            }
        }
    }

    /**
     * 向上传播分裂结果
     */
    private void insertIntoParent(List<BPlusNode> path, SplitResult result) {
        BPlusNode child = result.right;
        int promoteKey = result.key;

        for (int i = path.size() - 1; i >= 0; i--) {
            InternalNode parent = (InternalNode) path.get(i);
            int idx = parent.children.indexOf(result.left);

            // 插入提升的键和新的子节点
            parent.keys.add(idx, promoteKey);
            parent.children.add(idx + 1, child);

            if (!parent.isOverflow(order)) return;

            // 继续向上分裂
            SplitResult splitResult = parent.split();
            promoteKey = splitResult.key;
            child = splitResult.right;

            if (i == 0) {
                // 分裂到根节点
                InternalNode newRoot = new InternalNode();
                newRoot.keys.add(promoteKey);
                newRoot.children.add(splitResult.left);
                newRoot.children.add(child);
                root = newRoot;
                return;
            } else {
                result.left = splitResult.left;
            }
        }
    }

    /**
     * 精确查询
     */
    public Object get(int key) {
        return root.get(key);
    }

    /**
     * 范围查询(左闭右闭)
     */
    public List<Object> rangeQuery(int start, int end) {
        List<Object> result = new ArrayList<>();
        LeafNode node = head;
        while (node != null) {
            for (int i = 0; i < node.keys.size(); i++) {
                int key = node.keys.get(i);
                if (key > end) return result; // 超过范围直接返回
                if (key >= start) result.add(node.values.get(i));
            }
            node = node.next; // 通过链表访问下一个叶子节点
        }
        return result;
    }

    public static void main(String[] args) {
        // 创建5阶B+树
        BPlusTree tree = new BPlusTree(5);
        
        // 批量插入测试数据
        int[] keys = {5, 8, 10, 15, 20, 25, 30, 35, 40, 45, 50};
        String[] values = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"};
        for (int i = 0; i < keys.length; i++) {
            tree.insert(keys[i], values[i]);
        }

        // 查询测试
        System.out.println("精确查询:");
        System.out.println("key=25 -> " + tree.get(25)); // 输出 F
        
        System.out.println("\n范围查询[15,35]:");
        System.out.println(tree.rangeQuery(15, 35)); // 输出 [D, E, F, G, H]
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值