了解数据库的内部原理其实很不容易,大部分的读写都停留在理论文章上,因此肖哥带着大家使用Java手写一个完整的数据库,让大家了解数据库的解析器、性能分析器、认证、查询优化器,执行引擎、存储引擎、事务管理、MVCC,数据恢复等一系列功能。这个工作量比较大,属于每日1-2更新,大家如果想了解数据库的内容原理,掌握数据库的核心技术,那么可以跟着肖哥的步骤一步一步的学习。数据库会包含大家熟悉的数据结构与算法例如 B+树索引R树索引 等,仅数据存储就包含了数据块(Chunks)、文件头(File Header)、键值存储(MVMap)、并发控制、事务、序列化与反序列化、压缩、加密、索引、持久化存储、内存映射存储、分片机制、以及计划中的压缩和碎片整理等能力。

公号Solomon肖哥弹架构获取更多精彩内容 ,需要完整代码的可以留言

手写数据库持续更新系列 ,喜欢的读者 , 一定要订阅哦

欢迎 点赞,收藏,关注 。

完整的数据库具备能力图:

手写计划

  1. 小白能够看得懂的最简化版本数据库
  2. 标准SQL解析器
  3. 存储引擎
  4. 执行引擎
  5. 事务管理
  6. 日志系统
  7. 元数据管理
  8. 安全管理
  9. 性能分析器
  10. 网络版
  11. 标准JDBC接口对接

整体手写系列的大模块会包含以上功能。

支持数据类型版本

本案例它使用文件系统来实现数据的持久化,并引入了基本的查询优化和数据类型检查。

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

class Table {
    private List<Map<String, Object>> records = new ArrayList<>();
    private Map<String, Integer> columnTypes = new HashMap<>();

    public Table(List<String> columnNames, List<String> columnTypes) {
        this.columnNames = new ArrayList<>(columnNames);
        this.columnTypes.putAll(columnTypes.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> parseType(e.getValue()))));
    }

    private int parseType(String type) {
        switch (type.toUpperCase()) {
            case "INT":
                return Types.INTEGER;
            case "VARCHAR":
                return Types.VARCHAR;
            default:
                throw new IllegalArgumentException("Unsupported type: " + type);
        }
    }

    public void insert(Map<String, Object> record) {
        if (record.keySet().equals(columnNames)) {
            records.add(record);
        } else {
            throw new IllegalArgumentException("Column mismatch");
        }
    }

    public List<Map<String, Object>> select(Condition condition) {
        return records.stream()
            .filter(record -> condition.test(record))
            .collect(Collectors.toList());
    }

    public void saveToFile(String filename) throws IOException {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename))) {
            out.writeObject(new ArrayList<>(records));
        }
    }

    public void loadFromFile(String filename) throws IOException, ClassNotFoundException {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(filename))) {
            records = (List<Map<String, Object>>) in.readObject();
        }
    }

    interface Condition {
        boolean test(Map<String, Object> record);
    }
}

class SQLDatabase {
    private Map<String, Table> tables = new HashMap<>();
    private static final String DATA_DIR = "data";

    public void createTable(String tableName, List<String> columnNames, List<String> columnTypes) {
        Table table = new Table(columnNames, columnTypes);
        tables.put(tableName, table);
        new File(DATA_DIR).mkdirs();
        table.saveToFile(DATA_DIR + "/" + tableName + ".dat");
    }

    public void insertInto(String tableName, Map<String, Object> record) {
        Table table = tables.get(tableName);
        if (table != null) {
            table.insert(record);
            table.saveToFile(DATA_DIR + "/" + tableName + ".dat");
        } else {
            System.out.println("Table not found: " + tableName);
        }
    }

    public List<Map<String, Object>> selectFrom(String tableName, Table.Condition condition) {
        Table table = tables.get(tableName);
        if (table != null) {
            return table.select(condition);
        } else {
            System.out.println("Table not found: " + tableName);
            return Collections.emptyList();
        }
    }

    public void executeSQL(String sql) {
        // Simple SQL parser (naive implementation)
        // This is just a placeholder for a real SQL parser
        String[] parts = sql.trim().split("\s+");
        switch (parts[0].toUpperCase()) {
            case "CREATE":
                if (parts[1].toUpperCase().equals("TABLE")) {
                    String tableName = parts[2];
                    List<String> columnNames = new ArrayList<>();
                    List<String> columnTypes = new ArrayList<>();
                    for (int i = 3; i < parts.length; i++) {
                        if (parts[i].toUpperCase().equals("(")) {
                            i++;
                            while (i < parts.length && !parts[i].toUpperCase().equals(")")) {
                                columnNames.add(parts[i]);
                                i++;
                            }
                            break;
                        }
                    }
                    createTable(tableName, columnNames, columnTypes);
                }
                break;
            case "INSERT":
                if (parts[1].toUpperCase().equals("INTO")) {
                    String insertTableName = parts[2];
                    Map<String, Object> record = new HashMap<>();
                    int valueIndex = parts[3].toUpperCase().equals("VALUES") ? 4 : 3;
                    for (int i = valueIndex; i < parts.length; i++) {
                        String[] keyValue = parts[i].split("=");
                        record.put(keyValue[0].trim(), parseValue(keyValue[1].trim()));
                    }
                    insertInto(insertTableName, record);
                }
                break;
            case "SELECT":
                if (parts[1].toUpperCase().equals("FROM")) {
                    String selectTableName = parts[2];
                    Table.Condition condition = null;
                    if (parts.length > 3) {
                        condition = record -> record.get(parts[3].split("=")[0].trim()).equals(parts[3].split("=")[1].trim());
                    }
                    List<Map<String, Object>> selectedData = selectFrom(selectTableName, condition);
                    selectedData.forEach(System.out::println);
                }
                break;
            default:
                System.out.println("Unsupported command: " + parts[0]);
        }
    }

    private Object parseValue(String value) {
        if (value.matches("-?\d+")) {
            return Integer.parseInt(value);
        } else {
            return value;
        }
    }
}

// Database main class
public class Main {
    public static void main(String[] args) {
        SQLDatabase db = new SQLDatabase();

        // 执行SQL语句
        db.executeSQL("CREATE TABLE users (id INT, name VARCHAR(255), age INT);");
        db.executeSQL("INSERT INTO users VALUES (1, 'Alice', 25);");
        db.executeSQL("INSERT INTO users VALUES (2, 'Bob', 30);");
        db.executeSQL("SELECT * FROM users WHERE age = 25;");
    }
}

本案例实现了以下功能:

  • 数据持久化:使用Java的序列化机制将表数据保存到文件中。
  • 简单的SQL解析器:解析CREATE TABLEINSERT INTOSELECT语句。
  • 条件查询:实现了基于单个条件的查询功能。
  • 错误处理:在执行SQL语句时添加了基本的错误处理。

后续再次优化支持,暂时不支持如下:

  • 它不支持完整的SQL语法,例如子查询、连接、聚合函数等。
  • 查询优化非常基础,没有实现索引或更高级的查询优化技术。
  • 错误处理很基础,没有实现完整的SQL异常处理。
  • 数据类型支持有限,只支持整数和字符串。