注册
DM8数据库读写分离测试
技术分享/ 文章详情 /

DM8数据库读写分离测试

赵国伟 2025/06/13 112 1 0

一、测试环境

1.1数据库信息

1749189526583.png
查看数据库集群状态信息
image.png

1.2测试程序信息

1749189631745.png
DMRWBalanceTestSingle.java

package org.example;

import java.sql.*;

public class DMRWBalanceTestSingle {
    // JDBC驱动配置(需替换实际IP和端口)
    static final String JDBC_URL = "jdbc:dm://jmtest?jmtest=(10.12.10.119:5236,10.12.10.120:5236,10.12.10.121:5236)&rwPercent=25&rwSeparate=true&LOGIN_MODE=(1)";
    static final String USER = "dmtest";
    static final String PASSWORD = "Dameng_123";
    Connection conn = null;
    // 数据库写操作
    public void update_table (int p_i) {
        try {
            // 1.加载达梦驱动
            Class.forName("dm.jdbc.driver.DmDriver");
            // 2.建立连接
            conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
            // 3.关闭自动提交
            conn.setAutoCommit(false);
            // 4.同一个事务执行读写读操作
            Statement stmt = conn.createStatement();
            ResultSet rs0 = stmt.executeQuery("SELECT MAX(ID),'BEFORE' FROM TEST_TABLE");
            while(rs0.next()) {
                System.out.println("写会话同事务写前读操作:" + rs0.getInt(1));
            }
            stmt.executeUpdate("INSERT INTO TEST_TABLE VALUES(" + p_i +",'test_data"+p_i+"')");
            System.out.println("写会话同事务写操作");
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    // 数据库清理操作
    public void truncate_table() {
        try {
        // 1.加载达梦驱动
        Class.forName("dm.jdbc.driver.DmDriver");
        // 2.建立连接
        conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
        // 3.清空表
        Statement stmt = conn.createStatement();
        stmt.execute("TRUNCATE TABLE TEST_TABLE");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    // 数据库读操作
    public void select_table () {
        try {
            // 1.加载达梦驱动
            Class.forName("dm.jdbc.driver.DmDriver");
            // 2.建立连接
            conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
            // 3.读会话进行查询
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT SUM(ID) FROM TEST_TABLE");
            if(rs.next()) {
                System.out.println("读会话ID之和为:" + rs.getInt(1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

SingleTransaction.java

package org.example;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class SingleTransaction {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private int data;

    // 调用写操作(Update)
    public void update(int p_i) {
        rwLock.writeLock().lock();
        try {
            DMRWBalanceTestSingle dmbt = new DMRWBalanceTestSingle();
            dmbt.update_table(p_i);
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    // 调用读操作(Select)
    public void select() {
        rwLock.readLock().lock();
        try {
            DMRWBalanceTestSingle dmbt = new DMRWBalanceTestSingle();
            dmbt.select_table();
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        SingleTransaction singleTransaction = new SingleTransaction();
        // 启动线程前清空表数据
        DMRWBalanceTestSingle dmbt1 = new DMRWBalanceTestSingle();
        dmbt1.truncate_table();
        // 启动写线程(thread1)
        Thread thread1 = new Thread(() -> {
            for (int i = 1; i < 101; i++) {
                singleTransaction.update(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 启动读线程(thread2)
        Thread thread2 = new Thread(() -> {
            for (int i = 1; i < 101; i++) {
                singleTransaction.select();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

DMRWBalanceTest.java

package org.example;

import java.sql.*;

public class DMRWBalanceTest {
    // JDBC驱动配置(需替换实际IP和端口)
    static final String JDBC_URL = "jdbc:dm://jmtest?jmtest=(10.12.10.119:5236,10.12.10.120:5236,10.12.10.121:5236)&rwPercent=25&rwSeparate=true&LOGIN_MODE=(1)";
    static final String USER = "dmtest";
    static final String PASSWORD = "Dameng_123";
    Connection conn = null;
    // 数据库写操作
    public void update_table (int p_i) {
        try {
            // 1.加载达梦驱动
            Class.forName("dm.jdbc.driver.DmDriver");

            // 2.建立连接
            conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
            // 3.读写混合事务前先执行读事务
            Statement stmt = conn.createStatement();
            ResultSet rs0 = stmt.executeQuery("SELECT MIN(ID),'BEFORE' FROM TEST_TABLE");

            // 3.开启手动提交确保同一个事务包含读写操作
            conn.setAutoCommit(false);
            // 4.同一个事务执行读写读操作
            ResultSet rs1 = stmt.executeQuery("SELECT MAX(ID),'BEFORE' FROM TEST_TABLE");
            while(rs1.next()) {
                System.out.println("写会话同事务写前读操作:" + rs1.getInt(1));
            }
            stmt.executeUpdate("INSERT INTO TEST_TABLE VALUES(" + p_i +",'test_data"+p_i+"')");
            System.out.println("写会话同事务写操作");

            // 5.提交事务
            conn.commit();

            // 6.提交后再次查询(应路由到备库)
            ResultSet rs2 = stmt.executeQuery("SELECT COUNT(*) FROM TEST_TABLE");
            if(rs2.next()) {
                System.out.println("写会话事务提交后读操作:" + rs2.getInt(1));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    // 数据库清理操作
    public void truncate_table() {
        try {
        // 1.加载达梦驱动
        Class.forName("dm.jdbc.driver.DmDriver");

        // 2.建立连接
        conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);

        // 3.清空表
        Statement stmt = conn.createStatement();
        stmt.execute("TRUNCATE TABLE TEST_TABLE");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    // 数据库读操作
    public void select_table () {
        try {
            // 1.加载达梦驱动
            Class.forName("dm.jdbc.driver.DmDriver");

            // 2.建立连接
            conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);

            // 3.读会话进行三次查询
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT SUM(ID) FROM TEST_TABLE");
            if(rs.next()) {
                System.out.println("读会话第一次读操作,ID之和为:" + rs.getInt(1));
            }

            ResultSet rs1 = stmt.executeQuery("SELECT ID,NAME FROM TEST_TABLE LIMIT 1");
            if(rs1.next()) {
                System.out.println("读会话第二次读操作,ID=" + rs1.getInt(1)+",name="+rs1.getString(2));
            }

            ResultSet rs2 = stmt.executeQuery("SELECT NAME,ID FROM TEST_TABLE LIMIT 1");
            if(rs2.next()) {
                System.out.println("读会话第三次读操作,ID=" + rs2.getInt(2)+",name="+rs2.getString(1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

MultiTransaction.java

package org.example;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MultiTransaction {
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private int data;

    // 调用写操作(Update)
    public void update(int p_i) {
        rwLock.writeLock().lock();
        try {
            DMRWBalanceTest dmbt = new DMRWBalanceTest();
            dmbt.update_table(p_i);
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    // 调用读操作(Select)
    public void select() {
        rwLock.readLock().lock();
        try {
            DMRWBalanceTest dmbt = new DMRWBalanceTest();
            dmbt.select_table();
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        MultiTransaction MultiTransaction = new MultiTransaction();
        // 启动线程前清空表数据
        DMRWBalanceTest dmbt1 = new DMRWBalanceTest();
        dmbt1.truncate_table();
        // 启动写线程(thread1)
        Thread thread1 = new Thread(() -> {
            for (int i = 1; i < 101; i++) {
                MultiTransaction.update(i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 启动读线程(thread2)
        Thread thread2 = new Thread(() -> {
            for (int i = 1; i < 101; i++) {
                MultiTransaction.select();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

1.3测试数据初始化

SQL> create tablespace dmtest_tbs datafile 'dmtest_tbs.dbf' size 512 autoextend on 
next 128;
操作已执行
已用时间: 51.500(毫秒). 执行号:677717.
SQL> create user dmtest identified by Dameng_123 default tablespace dmtest_tbs;
操作已执行
已用时间: 9.599(毫秒). 执行号:677718.
SQL> grant dba to dmtest;
-- 创建测试表
[dmdba@DM8-PRIMARY PROD]$ disql dmtest/Dameng_123

服务器[LOCALHOST:5236]:处于主库打开状态
登录使用时间 : 6.636(ms)
disql V8
SQL> CREATE TABLE TEST_TABLE (id INT PRIMARY KEY, name VARCHAR(50));
操作已执行
已用时间: 46.045(毫秒). 执行号:801.
SQL> 

1.4测试方案信息

1749189832996.png

二、测试内容

2.1测试用例1(rwPercent=0&多会话单事务)

测试条件:rwPercent=0,执行多会话单事务测试代码 SingleTransaction.java
校验命令:

cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MIN(ID)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MAX(ID),'BEFORE'"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[INS] INSERT INTO TEST_TABLE"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MAX(ID),'AFTER'"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT COUNT(*)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT SUM(ID)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT ID,NAME"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT NAME,ID"|wc -l 

测试结果:
Primary:
image.png
Standby:
image.png
Standby2:
image.png

结论:
     当读写分配比例设置为0时读写同事务中的1条读sql分配到sty2备机执行1条写sql会被分配到主机执行,读事务1条读sql被分配到sty1执行。两个备机执行的读sql均为100条,分发很均衡。

2.2测试用例2(rwPercent=0&多会话多事务)

测试条件:rwPercent=0,执行多会话多事务测试代码 MultiTransaction.java
校验命令:

cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MIN(ID)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MAX(ID),'BEFORE'"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[INS] INSERT INTO TEST_TABLE"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MAX(ID),'AFTER'"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT COUNT(*)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT SUM(ID)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT ID,NAME"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT NAME,ID"|wc -l 

测试结果:
Primary:
image.png
Standby:
image.png
Standby2:
image.png
结论:
     当读写分配比例设置为0时读写同事务中的3条读sql分配到sty1备机执行1写条sql被分配到主机执行,读事务3条sql被分配到sty2执行。两个备机执行读sql量均为300条,分发很均衡。

2.3测试用例3(rwPercent=25&多会话单事务)

测试条件:rwPercent=25,执行多会话单事务测试代码 SingleTransaction.java
校验命令:

cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MIN(ID)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MAX(ID),'BEFORE'"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[INS] INSERT INTO TEST_TABLE"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MAX(ID),'AFTER'"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT COUNT(*)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT SUM(ID)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT ID,NAME"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT NAME,ID"|wc -l 

测试结果:
Primary:
image.png
Standby:
image.png
Standby2:
image.png
结论:
     当读写分配比例设置为25时读写同事务中的1条读sql分配到sty1备机执行1条写sql会被分配到主机执行,读事务1条读sql被分配到sty2执行。两个备机执行的读sql均为100条,主库执行sql为100条写入 100/300 > 25% 但写入必须全部在主库进行此时参数失效。

2.4测试用例4(rwPercent=25&多会话多事务)

测试条件:rwPercent=25,执行多会话多事务测试代码 MultiTransaction.java
校验命令:

cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MIN(ID)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MAX(ID),'BEFORE'"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[INS] INSERT INTO TEST_TABLE"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT MAX(ID),'AFTER'"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT COUNT(*)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT SUM(ID)"|wc -l 
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT ID,NAME"|wc -l
cat dmsql*.log|grep -i -F "EXECTIME:" |grep -i -F "[SEL] SELECT NAME,ID"|wc -l 

测试结果:
Primary:
image.png
Standby:
image.png
Standby2:
image.png
结论:
     当读写分配比例设置为25时读写同事务中的3条读sql 50此执行被分配到pri 250次执行分配到了sty1 ,读事务3条sql 49次执行被分配到了pri 251次被分配到了sty2 。读sql共计执行了 600 次其中pri(99) / sty1(250) / sty2(251) 写sql共计 100 次均在pri ,分发到主库的事务比例 199/700 = 28% 超过了配置的分发比例 25%。

三、测试结论

该结论仅为有限资源环境下简单测试得出的结论并不能完全反应复杂高并发场景下完整的读写分离分发流程
1、当写操作占所有操作比例超过 rwPercent 设定值时 rwPercent参数失效所有读操作均被分配到备库。
2、当写操作占所有操作比例低于 rwPercent 设定值时部分读被分配到了主库执行但本次测试整体分发比例超过了 rwPercent 设定的值。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服