Logo
Hint

Hint #

SQL HINT 就是通过在 SQL 语句上增加注释,从而实现强制路由的一种 Hint 方式。它降低了用户改造代码的成本。通过注释的方式我们可以方便地将 SQL 直接送达指定数据库执行而无视 Engine 的功能逻辑。 以多租户场景为例,用户不用再配置复杂的分库逻辑,也无需改造业务逻辑,只需要将指定库添加到注释信息中即可。 SQL Hint 注释格式暂时只支持 /* */,内容需要以 SPHEREEX_HINT 开始,Engine 也保持了对 ShardingSphere SQL Hint 的兼容,支持以 SHARDINGSPHERE_HINT: 开始。

使用方法 #

使用 SQL Hint 功能,如果是 MySQL 客户端连接,需要添加 -c 选项保留注释,客户端默认是 --skip-comments 过滤注释。

目前 SQL HINT 支持以下功能。

数据分片 #

数据分片 SQL Hint 功能的可选属性包括:

  • {table}.SHARDING_DATABASE_VALUE:用于添加 {table} 表对应的数据源分片键值,多个属性使用逗号分隔;
  • {table}.SHARDING_TABLE_VALUE:用于添加 {table} 表对应的表分片键值,多个属性使用逗号分隔。

分库不分表情况下,强制路由至某一个分库时,可使用 SHARDING_DATABASE_VALUE 方式设置分片,无需指定 {table}

数据分片 SQL Hint 功能的使用示例:

/* SPHEREEX_HINT: t_order.SHARDING_DATABASE_VALUE=1, t_order.SHARDING_TABLE_VALUE=1 */ SELECT * FROM t_order;

读写分离 #

读写分离 SQL Hint 功能的可选属性为 WRITE_ROUTE_ONLYtrue 表示将当前 SQL 强制路由到主库执行。

读写分离 SQL Hint 功能的使用示例:

/* SPHEREEX_HINT: WRITE_ROUTE_ONLY=true */ SELECT * FROM t_order;

数据源透传 #

数据源透传 SQL Hint 功能可选属性为 DATA_SOURCE_NAME,需要指定注册在 ShardingSphere 逻辑库中的数据源名称。

数据源透传 SQL Hint 功能的使用示例:

/* SPHEREEX_HINT: DATA_SOURCE_NAME=ds_0 */ SELECT * FROM t_order;

跳过 SQL 改写 #

跳过 SQL 改写 SQL Hint 功能可选属性为 SKIP_SQL_REWRITEtrue 表示跳过当前 SQL 的改写阶段。

跳过 SQL 改写 SQL Hint 功能的使用示例:

/* SPHEREEX_HINT: SKIP_SQL_REWRITE=true */ SELECT * FROM t_order;

禁用 SQL 审计 #

禁用 SQL 审计 SQL Hint 功能可选属性为 DISABLE_AUDIT_NAMES,需要指定需要禁用的 SQL 审计算法名称,多个 SQL 审计算法需要使用逗号分隔。

禁用 SQL 审计 SQL Hint 功能的使用示例:

/* SPHEREEX_HINT: DISABLE_AUDIT_NAMES=sharding_key_required_auditor */ SELECT * FROM t_order;

影子库压测 #

影子库压测 SQL Hint 功能可选属性为 SHADOWtrue 表示将当前 SQL 路由至影子库数据源执行。

影子库压测 SQL Hint 功能的使用示例:

/* SPHEREEX_HINT: SHADOW=true */ SELECT * FROM t_order;

开启双路由 #

开启双路由 SQL Hint 功能可选属性为 USE_TRAFFICtrue 表示将当前 SQL 请求由 JDBC 接入端转发到 Proxy 接入端执行。

开启双路由 SQL Hint 功能的使用示例:

/* SPHEREEX_HINT: USE_TRAFFIC=true */ SELECT * FROM t_order;

SQL 翻译器 #

SQL 翻译器 SQL Hint 可以指定不同数据库的翻译 SQL,功能可选属性为 TRANSLATOR_DIALECT_{databaseType}databaseType 表示对应的数据库类型。 属性值用于指定对应数据库类型的方言 SQL,并且需要使用双引号,避免 SQL 中的逗号和 SQL Hint 分隔符冲突。

SQL 翻译器 SQL Hint 功能的使用示例:

-- 语法模板
/* SPHEREEX_HINT: TRANSLATOR_DIALECT_{databaseType}="{SQL}", TRANSLATOR_DIALECT_{databaseType}="{SQL}", */ SELECT * FROM t_order WHERE ROWNUM < 2;

-- 语法示例
/* SPHEREEX_HINT: TRANSLATOR_DIALECT_MySQL="SELECT * FROM t_order LIMIT 1", TRANSLATOR_DIALECT_PostgreSQL="SELECT * FROM t_order LIMIT 1", */ SELECT * FROM t_order WHERE ROWNUM < 2;

SQL 翻译器 SQL Hint 功能支持预编译 SQL 以提升性能,为了避免用户改造业务 SQL,DBPlusEngine 提供了 {SPHEREEX_PARAM} 占位符,它会在 DBPlusEngine 中被替换为 JDBC ? 占位符。 SQL Hint 中的预编译参数顺序需要和逻辑 SQL 参数顺序保持一致,DBPlusEngine 会自动复用逻辑 SQL 中的参数,预编译 SQL 在 MyBatis 中的使用示例如下,其他 DAO 框架可以使用对应的动态 SQL 语法。

<select id="select" resultMap="baseResultMap">
    /* SPHEREEX_HINT: TRANSLATOR_DIALECT_MySQL="
    SELECT * FROM `t_order`
    <where>
        <if test="orderId != null and orderId != ''">
            `order_id` = {SPHEREEX_PARAM}
        </if>
    </where>
    " */
    SELECT * FROM t_order
    <where>
        <if test="orderId != null and orderId != ''">
            order_id = #{orderId,jdbcType=BIGINT}
        </if>
    </where>
</select>

SQL 翻译过程中还可能会存在不同 SQL 方言写法不一致的情况,此时 SQL 参数的顺序难以保证。为了保证 SQL Hint 的灵活使用,DBPlusEngine 提供了 {SPHEREEX_PARAM:1} 占位符,允许用户定义 SQL Hint 中的占位符引用逻辑 SQL 中某个位置的参数。 参数位置从 1 开始,超过逻辑 SQL 中的参数个数则会抛出异常,如下展示了在 MyBatis 中的使用示例。


<select id="select" resultMap="baseResultMap">
    /* SPHEREEX_HINT: TRANSLATOR_DIALECT_MySQL="
    SELECT * FROM `t_order`
    <where>
        `user_id` = {SPHEREEX_PARAM:2}
        AND `order_id` = {SPHEREEX_PARAM:1}
    </where>
    " */
    SELECT * FROM t_order
    <where>
        order_id = #{orderId,jdbcType=BIGINT}
        AND user_id = #{userId,jdbcType=BIGINT}
    </where>
</select>

此外,考虑到大多数 DAO 框架都支持动态 SQL,当逻辑 SQL 和 Hint 中的方言 SQL 由于动态条件无法使用参数顺序进行引用时,可以考虑使用参数列名进行引用。

在实际使用中,发现 Oracle 中的 MERGE 语句转换到其他方言时,需要使用参数列名引用的方式,占位符语法格式为:{SPHEREEX_PARAM:t_order.order_id}t_order 为表名,order_id 为列名。 当引用的参数位于子查询生成的临时表中时,表名需要使用临时表名,列名需要使用子查询生成的列名称,如下示例中如果想引用 MERGE 语句 USING 子查询中的参数,需要使用 {SPHEREEX_PARAM:t2.userId}t2 为临时表名,userId 为子查询列名。

占位符中指定的表和列必须在逻辑 SQL 中使用了预编译参数,否则参数引用失败。 如下展示了在 MyBatis 中的使用示例,其他 DAO 框架可以使用对应的动态 SQL 语法。

目前优先适配了 Oracle MERGE 语句,其他语句在 SQL 翻译过程中按需提供支持。

<select id="merge">
    /* SPHEREEX_HINT: TRANSLATOR_DIALECT_MySQL="
    INSERT INTO t_order
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="orderId != null">
            order_id,
        </if>
        <if test="userId != null">
            user_id,
        </if>
        <if test="status != null">
            status,
        </if>
        <if test="merchantId != null">
            merchant_id,
        </if>
        <if test="remark != null">
            remark,
        </if>
        <if test="creationDate != null">
            creation_date,
        </if>
    </trim>
    <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
        <if test="orderId != null">
            {SPHEREEX_PARAM:t_order.order_id},
        </if>
        <if test="userId != null">
            {SPHEREEX_PARAM:t_order.user_id},
        </if>
        <if test="status != null">
            {SPHEREEX_PARAM:t_order.status},
        </if>
        <if test="merchantId != null">
            {SPHEREEX_PARAM:t_order.merchant_id},
        </if>
        <if test="remark != null">
            {SPHEREEX_PARAM:t_order.remark},
        </if>
        <if test="creationDate != null">
            {SPHEREEX_PARAM:t_order.creation_date}
        </if>
    </trim>
    ON DUPLICATE KEY UPDATE
    <if test="merchantId != null">
        merchant_id = {SPHEREEX_PARAM:t_order.merchant_id},
    </if>
    <if test="remark != null">
        remark = {SPHEREEX_PARAM:t_order.remark}
    </if>
    " */
    MERGE INTO t_order t1
    USING (
    SELECT #{userId, jdbcType=BIGINT} AS userId, #{orderId, jdbcType=BIGINT} AS orderId, #{status, jdbcType=CHAR} AS status
    FROM DUAL
    ) t2 ON (t1.user_id = t2.userId
    AND t1.status = t2.status
    AND t1.order_id = t2.orderId)
    WHEN MATCHED THEN UPDATE
    <set>
        <if test="merchantId != null">
            merchant_id = #{merchantId, jdbcType=BIGINT},
        </if>
        <if test="remark != null">
            remark = #{remark, jdbcType=VARCHAR}
        </if>
    </set>
    WHEN NOT MATCHED THEN INSERT
    <trim prefix="(" suffix=")" suffixOverrides="," >
        <if test="orderId != null">
            order_id,
        </if>
        <if test="userId != null">
            user_id,
        </if>
        <if test="status != null">
            status,
        </if>
        <if test="merchantId != null">
            merchant_id,
        </if>
        <if test="remark != null">
            remark,
        </if>
        <if test="creationDate != null">
            creation_date,
        </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
        <if test="orderId != null">
            #{orderId, jdbcType=BIGINT},
        </if>
        <if test="userId != null">
            #{userId, jdbcType=BIGINT},
        </if>
        <if test="status != null">
            #{status, jdbcType=CHAR},
        </if>
        <if test="merchantId != null">
            #{merchantId, jdbcType=BIGINT},
        </if>
        <if test="remark != null">
            #{remark, jdbcType=VARCHAR},
        </if>
        <if test="creationDate != null">
            #{creationDate, jdbcType=TIMESTAMP},
        </if>
    </trim>
</select>

跳过数据脱敏 #

支持跳过数据脱敏,查询结果返回明文。内容需要以 SPHEREEX_HINT 开始,属性名为 SKIP_SQL_MASK。

/* SPHEREEX_HINT: SKIP_SQL_MASK=true */
SELECT * FROM t_order;