非规范SQL的sharding-jdbc实践

《“分库分表" ?选型和流程要慎重,不然会失控》中,咱们谈处处于驱动层的sharding-jdbc。开源作到这个水平,已经超棒了,不像tddl成了个太监。但仍是有坑。java

不过不能怪框架,毕竟有些sql,只有程序和鬼能懂。python

<select id="getCodes" resultMap="BaseResultMap" parameterType="java.util.Map">
    <foreach collection="orderCodes" index="index" item="item" open="" separator="union all" close="">
        select
      	<include refid="Base_Column_List"/>
       	from order
       	where  orderCode =  #{item}
    </foreach>
</select>
复制代码

不支持的操做

分库分表后,就成为了一个阉割型的数据库。不少sql的特性是不支持的,须要使用其余手段改进。如下以3.0.0版本进行描述。spring

distinct

sharding-jdbc不支持distinct,单表可以使用group by进行替代。多表联查可以使用exists替代sql

select DISTINCT
        a, b, c, d
        from  table
        where df=0
复制代码

改为数据库

select a, b, c, d
        from  table
        where df=0
        group by a, b, c, d
复制代码

having

sharding-jdbc不支持having,可以使用嵌套子查询进行替代bash

union

sharding-jdbc不支持union(all),可拆分红多个查询,在程序拼接mybatis

关于子查询

sharding-jdbc不支持在子查询中出现一样的表,如 如下能够==>app

SELECT COUNT(*) FROM (SELECT * FROM t_order o)
复制代码

如下报错==>框架

SELECT COUNT(*) FROM (SELECT * FROM t_order o WHERE o.id IN (SELECT id FROM t_order WHERE status = ?))
复制代码

因为归并的限制,子查询中包含聚合函数目前没法支持。函数

mybatis 注释

sharding-jdbc不支持sql中的<!-- – >注释,如必须使用则写在sql前,或使用/* */

不支持text字段

改成varchar,好几年的bug了,可是没改

case when

某些case when是不支持的,好比不在聚合函数中的case when,须要将这部分sql逻辑写到程序里。

case when不该该是DBA禁用的函数么?咱们在填坑

一些奇怪的反应

这个是能够的

select  a-b from dual  
复制代码

但这个不能够...

select (a-b)c from dual  
复制代码

sharding 也不支持以下形式查询,解析紊乱

and (1=1 or 1=1)
复制代码

关于分页

严禁无切分键的深分页!由于会对SQL进行如下解释,而后在内存运行。

select *  from a limit 10 offset 1000
复制代码

=======>

Actual SQL:db0 ::: select *  from a limit 1010 offset 0
复制代码

关于表名

表名需与sharding-jdbc配置一致,推荐均为小写。由于路由是放在hashmap里的,没有区分大小写...因此若是你的sql写错了会找不到。

配置冗余

每一张表都要配置路由信息才可以被正确解析,若是你库里的表太多,这个配置文件会膨胀的特别大,上千行也是有的。因此在yml中能够将配置文件分开。

spring.profiles.include: sharding
复制代码

如何扫多库

好比一些定时任务,须要遍历全部库。

方法1:遍历全部库

使用如下方式拿到真正的数据库列表

Map<String, DataSource> map = ShardingDataSource.class.cast(dataSource).getDataSourceMap();
复制代码

而后在每个库上执行扫描逻辑。这种状况下没法使用mybaits,须要写原生jdbc

方法2:根据切分键遍历

此种方法会拿到一个切分键的列表,好比日期等。而后经过遍历这个列表执行业务逻辑。此种方法在列表特别大的时候执行会比较缓慢。

如何验证

分库分表很危险,由于一旦数据入错库,后续的修理很麻烦。因此刚开始能够将路由信息指向到源表,即:只验证SQL路由的准确性。等待全部的SQL路由都验证经过,再切换到真正的分库或者表。

确保可以打印SQL

sharding.jdbc.config.sharding.props.sql.show: true
复制代码

将sql打印到单独的文件(logback)

<appender name="SQL" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>${LOG_HOME}/sharding.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${LOG_HOME}/backup/sharding.log.%d{yyyy-MM-dd}
    </fileNamePattern>
    <maxHistory>100</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    <pattern>${ENCODER_PATTERN}</pattern>
</encoder>
</appender>
复制代码

写一些脚本进行SQL文件的验证。我这里有个通用的,你能够改下你的逻辑。

import sys
import re
import getopt

def process(SQL):
    one= "".join(line.strip().replace("\n", " ") for line in SQL)
    place = [m.groups()[0] if m.groups()[0] else m.groups()[1] for m in re.finditer(r"[ ]+(\w+)[ ]*=[ ]*\?|(\?)", one)]

    if len(place):
        mat = re.search(r"::: \[\[(.*)\]\]", one)
        if mat is not None:
            vals = [str(i).strip() for i in str(mat.groups()[0]).split(',')]
            if "splitKey" in place:
                for i in range(len(place)):
                    part = place[i]
                    //这里写你的逻辑
            else:
                 print("no splitKey", one)

SQL = []
def process_line(line):
    global SQL
    if "Actual SQL" in line:
        SQL = []
        SQL.append(line)
    else:
        if line.strip().endswith("]]"):
            SQL.append(line)
            process(SQL)
            SQL = []
        else:
            SQL.append(line)

opts, args = getopt.getopt(sys.argv[1:], "bf")

for op, value in opts:
    if op == "-b":
        print("enter comman mode , such as 'python x.py -b sharding.log > result'")
        with open(args[0], "rb") as f:
            for line in f:
                process_line(line)
    elif op== "-f":
    	print("enter stream scroll mode , such as 'python x.py -f sharding.log '")
        with open(args[0], "rb") as f:
            f.seek(0,2)
            while True:
                last_pos = f.tell()
                line = f.readline()
            if line: process_line(line)
复制代码

其余

你可能要常常切换路由,因此某些时候路由信息要放在云端可以动态修改。

哦对了,我这里还有一段开发阶段的验证代码,能让你快速验证SQL可否正确解析。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)

public class ShardingTest {
    @Autowired
    DataSource dataSource;

    @Test
    public void testGet() {
        try {
            Connection conn = dataSource.getConnection();
            PreparedStatement stmt;
            ResultSet rs;
            String sql = new String(Files.readAllBytes(Paths.get("/tmp/a.sql")));

            stmt = conn.prepareStatement(sql);
            rs = stmt.executeQuery();
            printRS(rs);

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    public static void printRS(ResultSet rs) throws Exception {
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnsNumber = rsmd.getColumnCount();
        while (rs.next()) {
            for (int i = 1; i <= columnsNumber; i++) {
                if (i > 1) System.out.print(", ");
                String columnValue = rs.getString(i);
                System.out.print(columnValue + " " + rsmd.getColumnName(i));
            }
            System.out.println("");
        }
    }
}
复制代码

有SQL规范的团队是幸福的,分库分表简单的很。而动辄几百行,有各类复杂函数的SQL,就只能一步一个坑了。

话说回来,若是不是为了事务这个特性,为了支持老掉牙的业务,谁会用这分完后人不像人,鬼不像鬼的东西。

相关文章
相关标签/搜索