MySQL(105) 如何进行数据库分片?

发布于:2025-07-01 ⋅ 阅读:(24) ⋅ 点赞:(0)

数据库分片(Sharding)是一种将数据库表的数据分布到多个物理数据库实例上的技术,以提高数据库的性能和可扩展性。下面将详细介绍如何在Java中实现数据库分片,包括分片策略、分片管理和数据访问。

1. 环境准备

假设我们使用Spring Boot和MySQL,并且需要分片的表是users表。

2. 分片策略

常见的分片策略有哈希分片(Hash Sharding)、范围分片(Range Sharding)和列表分片(List Sharding)。这里我们以哈希分片为例,根据用户ID进行分片。

3. 项目依赖

首先,确保在pom.xml中添加必要的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

4. 配置数据源

application.properties中配置多个数据源:

spring.datasource.primary.url=jdbc:mysql://localhost:3306/db_shard_0
spring.datasource.primary.username=root
spring.datasource.primary.password=password
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.secondary.url=jdbc:mysql://localhost:3306/db_shard_1
spring.datasource.secondary.username=root
spring.datasource.secondary.password=password
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

5. 数据源配置类

使用Java代码配置数据源:

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    @Bean(name = "ds0")
    public DataSource dataSource0() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:3306/db_shard_0")
                .username("root")
                .password("password")
                .driverClassName("com.mysql.cj.jdbc.Driver")
                .build();
    }

    @Bean(name = "ds1")
    public DataSource dataSource1() {
        return DataSourceBuilder.create()
                .url("jdbc:mysql://localhost:3306/db_shard_1")
                .username("root")
                .password("password")
                .driverClassName("com.mysql.cj.jdbc.Driver")
                .build();
    }

    @Bean
    public DataSource routingDataSource(@Qualifier("ds0") DataSource ds0, @Qualifier("ds1") DataSource ds1) {
        AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
            @Override
            protected Object determineCurrentLookupKey() {
                return ShardContextHolder.getShard(); // 从上下文中获取当前分片键
            }
        };

        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("ds0", ds0);
        targetDataSources.put("ds1", ds1);
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(ds0);

        return routingDataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource routingDataSource) {
        return new JdbcTemplate(routingDataSource);
    }
}

6. 分片上下文

定义一个上下文来存储当前的分片信息:

public class ShardContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static void setShard(String shard) {
        contextHolder.set(shard);
    }

    public static String getShard() {
        return contextHolder.get();
    }

    public static void clearShard() {
        contextHolder.remove();
    }
}

7. 数据库操作

在进行数据库操作之前,根据分片策略设定当前的分片:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private String getShard(String userId) {
        int hash = userId.hashCode();
        int shardId = Math.abs(hash % 2); // 这里假设有2个分片
        return "ds" + shardId;
    }

    public void insertUser(String userId, String name, String email) {
        String shard = getShard(userId);
        ShardContextHolder.setShard(shard);
        String sql = "INSERT INTO users (user_id, name, email) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, userId, name, email);
        ShardContextHolder.clearShard();
    }

    public void getUser(String userId) {
        String shard = getShard(userId);
        ShardContextHolder.setShard(shard);
        String sql = "SELECT * FROM users WHERE user_id = ?";
        User user = jdbcTemplate.queryForObject(sql, new Object[]{userId}, (rs, rowNum) -> 
            new User(rs.getString("user_id"), rs.getString("name"), rs.getString("email")));
        ShardContextHolder.clearShard();
        return user;
    }
}

8. 测试

通过调用UserService中的方法进行测试:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class TestRunner implements CommandLineRunner {

    @Autowired
    private UserService userService;

    @Override
    public void run(String... args) throws Exception {
        userService.insertUser("user1", "Alice", "alice@example.com");
        userService.insertUser("user2", "Bob", "bob@example.com");

        User user1 = userService.getUser("user1");
        System.out.println(user1);

        User user2 = userService.getUser("user2");
        System.out.println(user2);
    }
}

结论

通过以上步骤,我们实现了基于Java的数据库分片。分片策略、数据源配置、分片上下文和数据库操作的组合,使得系统可以根据特定的分片键将数据分布到不同的物理数据库实例中,从而提升系统的性能和可扩展性。根据实际需求,可以选择不同的分片策略(如范围分片或列表分片),并进行相应的实现和优化。


网站公告

今日签到

点亮在社区的每一天
去签到