MySQL 读写分离

MySQL 读写分离

一、配置主库(Master)

1.修改主库的配置文件

修改主库的 my.cnf 配置文件,生成二进制日志 (binary log) 和服务器唯一ID ,这是实现主从复制的必要配置

shell 复制代码
[mysqld]
# skip-grant-tables
user=root
port=3306
basedir=/usr/local/mysql
datadir=/data/mysql
socket=/usr/local/mysql/socket/mysql.sock
# Disabling symbolic-links is recommended to prevent assorted security risks
# Settings user and group are ignored when systemd is used.
# If you need to run mysqld under a different user or group,
# customize your systemd unit file for mariadb according to the
# instructions in http://fedoraproject.org/wiki/Systemd
log-error=/usr/local/mysql/logs/mysqld.log
pid_file=/var/run/mysqld/mysqld.pid
symbolic-links=0


# master 配置
server-id=1            # 主库的服务器ID,必须唯一
log-bin=mysqls-bin      # 开启二进制日志,文件名可选
binlog-format=ROW      # (可选,不指定默认 STATEMENT )使用行级复制(推荐)


# 需要重启mysql服务,master配置才会生效
2.创建用于复制的用户
  • 登录到Mysql 的主库中,创建一个专门的用户供从库使用,用于复制
shell 复制代码
# 创建用户
CREATE USER '用户名'@'%' IDENTIFIED BY '用户密码';
e.g
CREATE USER 'replica_user'@'%' IDENTIFIED BY 'replica123456';

# 给用户赋予 '复制' 的权限
GRANT REPLICATION SLAVE ON *.* TO '用户名'@'%';
e.g
GRANT REPLICATION SLAVE ON *.* TO 'replica_user'@'%';


# 这里必须要执行,否则在从库执行 SHOW SLAVE STATUS\G; 时会报错 2061
alter user '用户名'@'%' identified with mysql_native_password by 'mysql数据库登录密码';
e.g
alter user 'replica_user'@'%' identified with mysql_native_password by 'replica123456';

# 刷新权限
FLUSH PRIVILEGES;

Tip

从库配置启动 复制时报错

SHOW SLAVE STATUS\G;

报错:Last_IO_Errno: 2061

Last_IO_Error: error connecting to master 'replica_user@120.77.27.139:3306' - rery-time: 60 retries: 1 message: Authentication plugin 'caching_sha2_password' reported error: Auhentication requires secure connection.

3 获取主库的二进制日志位置
  • 锁住主库防止数据变化,获取当前的二进制文件名和位置
shell 复制代码
# 锁住主库
mysql> FLUSH TABLES WITH READ LOCK;
Query OK, 0 rows affected (0.01 sec)

# 获取当前二进制文件名和位置
mysql> SHOW MASTER STATUS;
+-------------------+----------+--------------+------------------+-------------------+
| File              | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------------+----------+--------------+------------------+-------------------+
| mysqls-bin.000001 |      157 |              |                  |                   |
+-------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

# 解锁主库
mysql> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)

二、配置从库(Slave)

1.修改从库的配置文件
  • 在从库的MySQL的配置文件 my.cnf 中进行必要的配置,确保其有唯一的服务器ID 并启用中继日志
shell 复制代码
[mysqld]
server-id=2          # 从库的服务器ID,确保唯一
relay-log=relay-bin  # 启用中继日志(用于接收主库的二进制日志)
replicate-do-db=tbb-iov # 指定从主库中需要复制的数据库
read-only=1 # 只读
2.连接到主库并启动复制
  • 登录从库,通过以下配置指定主库的信息并启动复制
java 复制代码
CHANGE MASTER TO
  MASTER_HOST='主库的IP地址',           # 主库的IP地址
  MASTER_PORT=3306,  -- 如果主库使用非默认端口,这里需要指定    
  MASTER_USER='replica_user',          # 复制用户
  MASTER_PASSWORD='replica_password',  # 复制用户的密码
  MASTER_LOG_FILE='mysql-bin.000001',  # 主库的二进制日志文件名
  MASTER_LOG_POS=154;                  # 主库的二进制日志位置

e.g
CHANGE MASTER TO
  MASTER_HOST='1x0.xx.xx.13x',
  MASTER_PORT=3306,
  MASTER_USER='replica_user',
  MASTER_PASSWORD='replica123456',
  MASTER_LOG_FILE='mysqls-bin.000001',
  MASTER_LOG_POS=157;
  • 启动从库的复制相关命令
sql 复制代码
# 启动
START SLAVE;
# 停止
STOP SLAVE;
# 查看同步状态
SHOW SLAVE STATUS\G;
  • 主从复制状态参数
shell 复制代码
               Slave_IO_State: Waiting for source to send event
                  Master_Host: xx.xx.xx.xx
                  Master_User: replica_user
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysqls-bin.000001
          Read_Master_Log_Pos: 7657
               Relay_Log_File: relay-bin.000006
                Relay_Log_Pos: 2135
        Relay_Master_Log_File: mysqls-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: tbb-iov,demo

Slave_IO_Running 和 Slave_SQL_Running 必须要为 Yes 才表示成功启动主从复制

  • 查询同步状态的参数说明
SQL 复制代码
连接和状态信息
Slave_IO_State: 当前 IO 线程的状态。例如,Waiting for source to send event 表示从库正在等待主库发送事件。
Master_Host: 主库的 IP 地址或主机名。
Master_User: 用于复制的用户名。
Master_Port: 主库的端口号。
Connect_Retry: 从库尝试重新连接到主库的间隔时间(秒)。
Master_Log_File: 当前正在读取的主库二进制日志文件。
Read_Master_Log_Pos: 当前读取的主库二进制日志的位置。
Relay_Log_File: 当前正在使用的中继日志文件。
Relay_Log_Pos: 当前中继日志的位置。
Relay_Master_Log_File: 当前中继日志对应的主库二进制日志文件。
Slave_IO_Running: IO 线程是否正在运行。Yes 表示正在运行,No 表示停止。
Slave_SQL_Running: SQL 线程是否正在运行。Yes 表示正在运行,No 表示停止。
Slave_SQL_Running_State: SQL 线程的当前状态。例如,Replica has read all relay log; waiting for more updates 表示从库已经读取了所有中继日志,正在等待更多的更新。

复制过滤规则
Replicate_Do_DB: 需要复制的数据库列表。
Replicate_Ignore_DB: 不需要复制的数据库列表。
Replicate_Do_Table: 需要复制的表列表。
Replicate_Ignore_Table: 不需要复制的表列表。
Replicate_Wild_Do_Table: 需要复制的表的通配符模式。
Replicate_Wild_Ignore_Table: 不需要复制的表的通配符模式。

错误信息
Last_Errno: 最近一次错误的错误码。
Last_Error: 最近一次错误的错误信息。
Skip_Counter: 跳过的错误事务计数器。
Exec_Master_Log_Pos: 当前已执行的主库二进制日志的位置。
Last_IO_Errno: 最近一次 IO 错误的错误码。
Last_IO_Error: 最近一次 IO 错误的错误信息。
Last_SQL_Errno: 最近一次 SQL 错误的错误码。
Last_SQL_Error: 最近一次 SQL 错误的错误信息。

其他信息
Relay_Log_Space: 中继日志占用的空间大小(字节)。
Until_Condition: 停止复制的条件。
Until_Log_File: 停止复制的日志文件。
Until_Log_Pos: 停止复制的日志位置。
Master_SSL_Allowed: 是否允许 SSL 连接。
Master_SSL_CA_File: SSL 证书颁发机构文件路径。
Master_SSL_CA_Path: SSL 证书颁发机构路径。
Master_SSL_Cert: SSL 证书文件路径。
Master_SSL_Cipher: SSL 密码套件。
Master_SSL_Key: SSL 私钥文件路径。
Seconds_Behind_Master: 从库落后于主库的时间(秒)。
Master_SSL_Verify_Server_Cert: 是否验证主库的 SSL 证书。
Master_Server_Id: 主库的服务器 ID。
Master_UUID: 主库的 UUID。
Master_Info_File: 存储主库信息的文件。
SQL_Delay: SQL 线程延迟时间(秒)。
SQL_Remaining_Delay: 剩余的延迟时间。
Replicate_Ignore_Server_Ids: 不需要复制的服务器 ID 列表。
Master_Retry_Count: 从库尝试重新连接到主库的最大次数。
Master_Bind: 绑定的网络接口。
Last_IO_Error_Timestamp: 最近一次 IO 错误的时间戳。
Last_SQL_Error_Timestamp: 最近一次 SQL 错误的时间戳。
Master_SSL_Crl: SSL 证书吊销列表文件路径。
Master_SSL_Crlpath: SSL 证书吊销列表路径。
Retrieved_Gtid_Set: 已检索的 GTID 集合。
Executed_Gtid_Set: 已执行的 GTID 集合。
Auto_Position: 是否启用自动定位(基于 GTID)。
Replicate_Rewrite_DB: 数据库重写规则。
Channel_Name: 复制通道名称。
Master_TLS_Version: 主库支持的 TLS 版本。
Master_public_key_path: 主库的公钥文件路径。
Get_master_public_key: 是否获取主库的公钥。
Network_Namespace: 网络命名空间。

Tip

数据库开启主从复制之前,主库和从库的数据需要保持一致,

三、Spring Boot + MySQL+ Mybatis 主从复制,读写分离

1、Maven 依赖引入

主要依赖如下

xml 复制代码
<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.6.13</spring-boot.version>
    <mybatis.version>2.2.2</mybatis.version>
    <mysql.version>8.0.30</mysql.version>
    <alibabadruid.version>1.2.16</alibabadruid.version>
    <lombok.version>1.18.26</lombok.version>

</properties>
<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>


    <!-- web依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- aop -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>${mybatis.version}</version>
    </dependency>

    <!-- mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>

    <!-- druid mysql数据库连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${alibabadruid.version}</version>
    </dependency>

    <!-- lombok 工具 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
    </dependency>

</dependencies>
2、mysql主从配置

自定义mysql 的主从数据源的连接参数,以及mybatis的配置

yaml 复制代码
server:
  port: 8082

# 自定义mysql配置
mysql:
  datasource:
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://ip1:port/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: xxx
      password: xxx
      initial-size: 5
      max-active: 20
      min-idle: 5
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      max-evictable-idle-time-millis: 900000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20
      filters: stat,wall
      use-global-data-source-stat: true
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

    slave:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://ip2:port/demo?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: xxxx # 这里的账号最好是只读权限的mysql用户,从库只负责读,不能写入数据
      password: xxxx
      initial-size: 5
      max-active: 25
      min-idle: 5
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      max-evictable-idle-time-millis: 900000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      pool-prepared-statements: true
      max-open-prepared-statements: 50
      max-pool-prepared-statement-per-connection-size: 20
      filters: stat,wall
      use-global-data-source-stat: true
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

# 指定mapper*.xml加载位置
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml
3、数据源配置
1. DataSourceConfig 数据源配置类
java 复制代码
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.datasource.demo.enums.DataSourceType;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import java.util.HashMap;
import java.util.Map;

/**
 * ClassName: DataSourceConfig
 * Package: com.datasource.demo.config
 * Description:
 * 数据源配置
 *
 * @Author wfk
 * @Create 2024/11/12 14:15
 * @Version 1.0
 */
@Configuration
public class DataSourceConfig {

    /**
     * 主库数据源
     *
     * @return
     */
    @Bean("master")
    @ConfigurationProperties(prefix = "mysql.datasource.master")
    public DruidDataSource dataSource1() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 从库数据源
     *
     * @return
     */
    @Bean("slave")
    @ConfigurationProperties(prefix = "mysql.datasource.slave")
    public DruidDataSource dataSource2() {
        return DruidDataSourceBuilder.create().build();
    }


    /**
     * 配置默认数据源
     *
     * @param masterDataSource
     * @param slaveDataSource
     *
     * 必须要加 @Primary 注解,优先下面的配置
     * @return
     */
    @Primary
    @Bean("dynamicDataSource")
    public DynamicDataSource dataSource(@Qualifier("master") DruidDataSource masterDataSource,
                                 @Qualifier("slave") DruidDataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.getName(), masterDataSource);
        targetDataSources.put(DataSourceType.SLAVE.getName(), slaveDataSource);
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        return dynamicDataSource;
    }
}

以上源码分析:

  • 采用了阿里云的Druid 数据库连接池,所以需要使用 DruidDataSource

  • @ConfigurationProperties(prefix = "mysql.datasource.slave") 加载 yml 配置文件的自定义属性,自定义参数名称需要和 druid 的配置的标准名称一样,不然无法自动加载

  • DynamicDataSource 继承了 抽象类 AbstractRoutingDataSource ,是实现动态数据源的核心类,将所有数据源注入到这个类中,通过 DataSourceContextHolder 修改数据源,实现动态切换。

  • @Primary 注解必须要加上,标记为优先使用的数据源

2.DataSourceContextHolder 数据源上下文
java 复制代码
/**
 * ClassName: DataSourceContextHolder
 * Package: com.datasource.demo.config.datasource
 * Description:
 * 本地线程,数据源上下文
 * @Author wfk
 * @Create 2024/11/12 14:45
 * @Version 1.0
 */
public class DataSourceContextHolder {

    // 定义一个 ThreadLocal 变量,用于保存当前线程的数据源标识
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置当前线程的数据源标识
    public static void setDataSource(String dataSource){
        contextHolder.set(dataSource);
    }

    // 获取当前线程的数据源标识
    public static String getDataSource(){
        return contextHolder.get();
    }

    // 清除当前线程的数据源标识
    public static void clearDataSource(){
        contextHolder.remove();
    }
}

以上源码分析:

  • ThreadLocal 是一个线程局部变量容器,每个线程都有自己独立的副本。这意味着每个线程都可以独立地设置和获取自己的 ThreadLocal 变量值,而不会影响其他线程。

  • setDataSource(String dataSource) 方法用于设置当前线程的数据源标识。调用这个方法时,传入的数据源标识会被保存在当前线程的 ThreadLocal 变量中。

  • getDataSource() 方法用于获取当前线程的数据源标识。这个方法通常在数据源路由逻辑中被调用,根据当前线程的数据源标识选择合适的数据源进行数据库操作。

  • clearDataSource() 方法用于清除当前线程的数据源标识。这个方法通常在请求处理完毕后被调用,以释放资源并防止内存泄漏。

3.DynamicDataSource 数据源路由
java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * ClassName: DynamicDataSource
 * Package: com.datasource.demo.config.datasource
 * Description:
 * 
 * @Author wfk
 * @Create 2024/11/12 14:42
 * @Version 1.0
 */
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = DataSourceContextHolder.getDataSource();
        log.info("当前数据源 {}", dataSource);
        return dataSource;
    }
}

以上源码分析:

  • AbstractRoutingDataSource 是 Spring 框架提供的一个抽象类,用于实现数据源的动态切换。它提供了一个 determineCurrentLookupKey 方法,该方法返回一个键值,用于从配置的数据源映射中查找当前应使用的数据源。

  • determineCurrentLookupKey 方法用于确定当前线程应该使用哪个数据源,通过调用DataSourceContextHolder.getDataSource() 来获取当前线程的数据源标识

4.DataSourceType 自定义数据源类别枚举
java 复制代码
public enum DataSourceType {
    MASTER("master"),
    SLAVE("slave");
    private String name;
    DataSourceType(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
4、数据源切换
1.自定义注解实现

自定义注解,作用于方法上,通过AOP切面拦截,根据注解的value所对应的数据库源类别,实现数据源切换。

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBDataSource {
    DataSourceType value() default DataSourceType.MASTER;
}
2.AOP切面实现
java 复制代码
import com.datasource.demo.annotions.DBDataSource;
import com.datasource.demo.config.datasource.DataSourceContextHolder;
import com.datasource.demo.enums.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.aspectj.lang.annotation.Aspect;

import java.lang.reflect.Method;

/**
 * ClassName: DataSourceAspect
 * Package: com.datasource.demo.aspect
 * Description:
 * 数据源AOP切面
 *
 * @Author wfk
 * @Create 2024/11/12 16:05
 * @Version 1.0
 */

@Slf4j
@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("execution(* com.datasource.demo.service..*.*(..))")
    public void aspect() {
    }

    @Before("aspect()")
    private void doBefore(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        Class<?> clazz = target.getClass();
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 获取方法名称
        String methodName = methodSignature.getName();
        // 获取方法参数列表
        Class<?>[] parameterTypes = methodSignature.getMethod().getParameterTypes();
        try {
            // 通过方法名称和参数列表可以唯一获取方法(可能有重载的同名方法)
            Method method = clazz.getMethod(methodName, parameterTypes);
            // 判断方法是否存在指定的注解
            if (method !=null && method.isAnnotationPresent(DBDataSource.class)){
                DBDataSource annotation = method.getAnnotation(DBDataSource.class);
                // 如果是从库那就切换当前线程的数据源
                if (DataSourceType.SLAVE == annotation.value()) {
                    DataSourceContextHolder.setDataSource(DataSourceType.SLAVE.getName());
                    return;
                }
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        // 默认是使用主库
        DataSourceContextHolder.setDataSource(DataSourceType.MASTER.getName());
    }
}

以上源码分析:

*1.@Pointcut("execution( com.datasource.demo.service....(...))")**

  • execution():这是定义切入点表达式的关键字,用来匹配Java方法的执行连接点。
  • *:第一个星号表示返回值类型,这里的星号意味着匹配任何返回类型的方法。'com.datasource.demo.service...*.*(...):这部分是切入点表达式的主体,指定了要匹配的方法的位置和名称。
    • com.datasource.demo.service:这是包名,指明了要匹配的方法所在的包。
    • ..:两个点号表示该包下的所有子包。
    • *.*:第一个星号代表类名,第二个星号代表方法名,这里使用两个星号表示匹配该包及其子包下所有类的所有方法。
    • (..):括号内的两个点号表示参数列表,这里表示匹配任何参数列表的方法。

2.方法调用和注解检查

  • 获取目标对象和方法签名。
  • 通过反射获取方法对象,并检查方法是否标注了 DBDataSource 注解。
  • 如果方法标注了 DBDataSource 注解且注解值为 DataSourceType.SLAVE,则设置当前线程的数据源为从库。
  • 否则,默认设置当前线程的数据源为主库。
3.最终实现如下

在service层的实现类中,添加注解,查询相关的业务,通过注解指定从库,写入数据默认使用主库,实现读写分离。

java 复制代码
@DBDataSource(DataSourceType.SLAVE)
@Override
public List<BookInfoPO> getBookInfoList() {
    return bookInfoMapper.getBookInfoListMapper();
}
总结

关于数据源切换还有很多种方式

  • 在mapper层做拦截,对insert、update、delete 和 select 完全分离,可以通过MyCat、shardingsphere 等数据库中间件实现自动分离。
  • 也可以通过规范方法名前缀,对get、find、query 开头等方法进行拦截,也可以实现读写分离。
相关推荐
程序员岳焱31 分钟前
Java 与 MySQL 性能优化:Java 实现百万数据分批次插入的最佳实践
后端·mysql·性能优化
梦在深巷、2 小时前
MySQL/MariaDB数据库主从复制之基于二进制日志的方式
linux·数据库·mysql·mariadb
Johny_Zhao3 小时前
Ubuntu系统安装部署Pandawiki智能知识库
linux·mysql·网络安全·信息安全·云计算·shell·yum源·系统运维·itsm·pandawiki
祁思妙想3 小时前
八股学习(三)---MySQL
数据库·学习·mysql
惊骇世俗王某人4 小时前
1.MySQL之如何定位慢查询
数据库·mysql
叁沐4 小时前
MySQL 04 深入浅出索引(上)
mysql
q9085447035 小时前
MySQL 二进制日志binlog解析
mysql·binlog·binlog2sql·my2sql
码不停蹄的玄黓6 小时前
MySQL分布式ID冲突详解:场景、原因与解决方案
数据库·分布式·mysql·id冲突
帧栈7 小时前
mysql基础(一)快速上手篇
mysql
戒不掉的伤怀9 小时前
【Navicat 连接MySQL时出现错误1251:客户端不支持服务器请求的身份验证协议;请考虑升级MySQL客户端】
服务器·数据库·mysql