溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Druid多數(shù)據(jù)源下Sql防火墻導(dǎo)致異常的示例分析

發(fā)布時(shí)間:2021-09-10 17:28:10 來源:億速云 閱讀:292 作者:柒染 欄目:大數(shù)據(jù)

本篇文章給大家分享的是有關(guān)Druid多數(shù)據(jù)源下Sql防火墻導(dǎo)致異常的示例分析,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

問題原因

  1. 由于應(yīng)用組件同時(shí)使用了兩個(gè)數(shù)據(jù)源,Oracle和Mysql,而之前一直都是Oracle,近期引入了Mysql;

  2. 應(yīng)用組件生產(chǎn)環(huán)境配置開啟了Druid的Sql防火墻配置,配置項(xiàng)為:spring.datasource.druid.filter.wall.enabled=true ,由于測(cè)試環(huán)境未開啟,因此將問題帶到了生產(chǎn)上面。

開啟Sql防火墻后,在數(shù)據(jù)源初始化的時(shí)候會(huì)初始化一個(gè) WallFilter .

    @Bean
    @ConfigurationProperties("spring.datasource.druid.filter.wall")
    @ConditionalOnProperty(
        prefix = "spring.datasource.druid.filter.wall",
        name = {"enabled"}
    )
    @ConditionalOnMissingBean
    public WallFilter wallFilter(WallConfig wallConfig) {
        WallFilter filter = new WallFilter();
        filter.setConfig(wallConfig);
        return filter;
    }

問題表象

在近期版本上線后,一些跑了好幾年的sql突然一夜之間報(bào)錯(cuò)了,守版本的弟兄遇到這種問題后,難免大驚失色,無奈之下,當(dāng)晚只好回滾了版本。

下面我們來看下,已經(jīng)跑了幾年的sql當(dāng)時(shí)報(bào)了什么錯(cuò):

at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:798)
at com.alibaba.druid.wall.WallFilter.connection_prepareStatement(WallFilter.java:251)
at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:473)
at com.alibaba.druid.filter.FilterAdapter.connection_prepareStatement(FilterAdapter.java:929)
at com.alibaba.druid.filter.FilterEventAdapter.connection_prepareStatement(FilterEventAdapter.java:122)
at com.alibaba.druid.filter.FilterChainImpl.connection_prepareStatement(FilterChainImpl.java:473)
at com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl.prepareStatement(ConnectionProxyImpl.java:342)
at com.alibaba.druid.pool.DruidPooledConnection.prepareStatement(DruidPooledConnection.java:350)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.instantiateStatement(PreparedStatementHandler.java:87)
at org.apache.ibatis.executor.statement.BaseStatementHandler.prepare(BaseStatementHandler.java:88)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.prepare(RoutingStatementHandler.java:59)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:85)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:185)
at sun.reflect.GeneratedMethodAccessor408.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:433)
... 121 common frames omitted
Caused by: com.alibaba.druid.sql.parser.ParserException: syntax error, error in :' USING
(select ? as o', expect USING, actual IDENTIFIER pos 61, line 2, column 39, token IDENTIFIER USING
at com.alibaba.druid.sql.parser.SQLParser.printError(SQLParser.java:284)
at com.alibaba.druid.sql.parser.SQLParser.accept(SQLParser.java:292)
at com.alibaba.druid.sql.parser.SQLStatementParser.parseMerge(SQLStatementParser.java:2879)
at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:225)
at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:81)
at com.alibaba.druid.wall.WallProvider.checkInternal(WallProvider.java:622)
at com.alibaba.druid.wall.WallProvider.check(WallProvider.java:576)
at com.alibaba.druid.wall.WallFilter.checkInternal(WallFilter.java:785)
... 141 common frames omitted

看到上面的異常堆棧,拋出的異常的意思是:對(duì)該SQL進(jìn)行解析的時(shí)候期望接受的USING關(guān)鍵字,但實(shí)際上在關(guān)鍵字庫里面確沒有找到USING關(guān)鍵字,而是把USING當(dāng)成了標(biāo)識(shí)符IDENTIFIER.

問題分析

打開SQLStatementParser 及 SQLParser類源碼發(fā)現(xiàn)有以下信息:

com.alibaba.druid.sql.parser.SQLStatementParser#parseMerge

Druid多數(shù)據(jù)源下Sql防火墻導(dǎo)致異常的示例分析

關(guān)鍵問題在這里,為什么關(guān)鍵字的詞里沒有找到USING關(guān)鍵字? 通過debug發(fā)現(xiàn)在第一次訪問數(shù)據(jù)的時(shí)候會(huì)調(diào)用com.alibaba.druid.pool.DruidDataSource#init 方法, 該方法里有這一句:

public void init() throws SQLException {
        if (inited) {
            return;
        }

        final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new SQLException("interrupt", e);
        }

        boolean init = false;
        try {
            if (inited) {
                return;
            }

            initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());

            this.id = DruidDriver.createDataSourceId();
            if (this.id > 1) {
                long delta = (this.id - 1) * 100000;
                this.connectionIdSeed.addAndGet(delta);
                this.statementIdSeed.addAndGet(delta);
                this.resultSetIdSeed.addAndGet(delta);
                this.transactionIdSeed.addAndGet(delta);
            }

            if (this.jdbcUrl != null) {
                this.jdbcUrl = this.jdbcUrl.trim();
                initFromWrapDriverUrl();
            }
	    // 重點(diǎn)在這里
            for (Filter filter : filters) {
                filter.init(this);
            }

這句話的意思是初始化配置的Filter,其中我們啟用的WallFilter就是在這里初始化的。

接下來我們進(jìn)入WallFilter 的 init方法:

 @Override
    public synchronized void init(DataSourceProxy dataSource) {

        if (null == dataSource) {
            LOG.error("dataSource should not be null");
            return;
        }

        if (this.dbType == null || this.dbType.trim().length() == 0) {						// dbType 取決于第一次調(diào)用此方法的數(shù)據(jù)源的類型
            if (dataSource.getDbType() != null) {
                this.dbType = dataSource.getDbType();
            } else {
                this.dbType = JdbcUtils.getDbType(dataSource.getRawJdbcUrl(), "");
            }
        }

        if (dbType == null) {
            dbType = JdbcUtils.getDbType(dataSource.getUrl(), null);
        }

        if (JdbcUtils.MYSQL.equals(dbType) || //
            JdbcUtils.MARIADB.equals(dbType) || //
            JdbcUtils.H2.equals(dbType)) {
            if (config == null) {
                config = new WallConfig(MySqlWallProvider.DEFAULT_CONFIG_DIR);
            }
						// 初始化Mysql類型的防火墻
            provider = new MySqlWallProvider(config);
        } else if (JdbcUtils.ORACLE.equals(dbType) || JdbcUtils.ALI_ORACLE.equals(dbType)) {
            if (config == null) {
                config = new WallConfig(OracleWallProvider.DEFAULT_CONFIG_DIR);
            }

我們發(fā)現(xiàn)此方法加了 synchronized,并且里面有一個(gè)關(guān)鍵的屬性 dbType。

由于WallFilter只有一個(gè)實(shí)例,此處的dbType是成員變量,代碼中判斷為null時(shí)才從當(dāng)前dataSource中獲??;

那么問題來了,我們現(xiàn)在有兩個(gè) DruidDataSource 實(shí)例,那么在初始化時(shí)就會(huì)調(diào)用WallFilter的初始化,但是盡管調(diào)用多次 WallFilterinit 方法,但是從上面代碼得知dbType僅取決于第一次 DruidDataSource 調(diào)用WallFilter 的 init 方法時(shí) dataSource.getDbType() 的值。

分析到這里,你看出什么問題了?

是的,如果當(dāng)我們第一個(gè)訪問的數(shù)據(jù)源是Mysql時(shí),那么不管后續(xù)的DruidDataSource多次調(diào)用WallFilter 的 init 方法,dbType永遠(yuǎn)不會(huì)改變。

所以導(dǎo)致,我們用的永遠(yuǎn)都是 MySqlWallProvider ,也就是說我們用的SQL防火墻是Mysql類型的,所以后續(xù)對(duì)SQL的語法解析也用到了MySqlLexer 詞法解析器,該解析器的關(guān)鍵字庫里面是沒有USING這個(gè)關(guān)鍵字的,所以會(huì)報(bào)錯(cuò) USING 語法錯(cuò)誤。

解決方案

開始我也說了,本次問題是部分實(shí)例報(bào)sql 異常,這又是為什么呢?

這是因?yàn)椋@和你第一次訪問的數(shù)據(jù)有關(guān),如果第一次訪問的是Mysql庫,那后面在訪問Oracle時(shí)候就會(huì)有這個(gè)問題。

如何解決這個(gè)問題呢?這里給出兩種方案。

  1. 可以把spring.datasource.druid.filter.wall.enabled=true 這個(gè)配置設(shè)置成false 或去掉該配置,這也是最簡(jiǎn)單快捷的方式;

  2. 對(duì)WallFilter和StatFilter 兩個(gè)類進(jìn)行拓展和相關(guān)邏輯優(yōu)化,需要編程,未實(shí)施。

以上就是Druid多數(shù)據(jù)源下Sql防火墻導(dǎo)致異常的示例分析,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI