You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by ji...@apache.org on 2023/03/07 06:32:21 UTC

[shardingsphere] branch master updated: Add DistSQL UNLOCK CLUSTER (#24483)

This is an automated email from the ASF dual-hosted git repository.

jianglongtao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 134a24a80df Add DistSQL UNLOCK CLUSTER (#24483)
134a24a80df is described below

commit 134a24a80df9ef1ec959ea217db8cfd6a6f4cc2f
Author: ChenJiaHao <Pa...@163.com>
AuthorDate: Tue Mar 7 14:32:02 2023 +0800

    Add DistSQL UNLOCK CLUSTER (#24483)
    
    * Add DistSQL UNLOCK CLUSTER
    
    * Fix code style
---
 distsql/parser/src/main/antlr4/imports/Keyword.g4  |  4 ++
 .../parser/src/main/antlr4/imports/RALStatement.g4 |  4 ++
 .../parser/autogen/KernelDistSQLStatement.g4       |  1 +
 .../core/kernel/KernelDistSQLStatementVisitor.java | 17 ++++--
 .../ral/updatable/UnlockClusterStatement.java      | 28 ++++++++++
 .../infra/state/ClusterStateContextTest.java       | 55 +++++++++++++++++++
 .../datasource/ShardingSphereDataSourceTest.java   |  3 +
 .../driver/state/DriverStateContextTest.java       |  2 +
 .../mode/manager/ContextManager.java               | 61 +++++++++++----------
 .../ral/updatable/UnlockClusterUpdater.java        | 64 ++++++++++++++++++++++
 .../lock/impl/ClusterWriteLockStrategy.java        |  2 +-
 .../backend/state/impl/ReadOnlyProxyState.java     |  3 +-
 .../backend/state/impl/UnavailableProxyState.java  |  3 +-
 ...ingsphere.distsql.handler.ral.update.RALUpdater |  1 +
 .../proxy/backend/context/ProxyContextTest.java    |  3 +
 .../ral/updatable/UnlockClusterUpdaterTest.java    | 56 +++++++++++++++++++
 16 files changed, 269 insertions(+), 38 deletions(-)

diff --git a/distsql/parser/src/main/antlr4/imports/Keyword.g4 b/distsql/parser/src/main/antlr4/imports/Keyword.g4
index 246204dcc5a..ed493046e4e 100644
--- a/distsql/parser/src/main/antlr4/imports/Keyword.g4
+++ b/distsql/parser/src/main/antlr4/imports/Keyword.g4
@@ -347,6 +347,10 @@ LOCK
     : L O C K
     ;
 
+UNLOCK
+    : U N L O C K
+    ;
+
 CLUSTER
     : C L U S T E R
     ;
diff --git a/distsql/parser/src/main/antlr4/imports/RALStatement.g4 b/distsql/parser/src/main/antlr4/imports/RALStatement.g4
index 867a6ea03f2..fba79333737 100644
--- a/distsql/parser/src/main/antlr4/imports/RALStatement.g4
+++ b/distsql/parser/src/main/antlr4/imports/RALStatement.g4
@@ -107,6 +107,10 @@ lockCluster
     : LOCK CLUSTER WITH lockStrategy
     ;
 
+unlockCluster
+    : UNLOCK CLUSTER
+    ;
+
 inventoryIncrementalRule
     : LP_ readDefinition? (COMMA_? writeDefinition)? (COMMA_? streamChannel)? RP_
     ;
diff --git a/distsql/parser/src/main/antlr4/org/apache/shardingsphere/distsql/parser/autogen/KernelDistSQLStatement.g4 b/distsql/parser/src/main/antlr4/org/apache/shardingsphere/distsql/parser/autogen/KernelDistSQLStatement.g4
index 212e77299db..842ab54e121 100644
--- a/distsql/parser/src/main/antlr4/org/apache/shardingsphere/distsql/parser/autogen/KernelDistSQLStatement.g4
+++ b/distsql/parser/src/main/antlr4/org/apache/shardingsphere/distsql/parser/autogen/KernelDistSQLStatement.g4
@@ -52,5 +52,6 @@ execute
     | showMigrationRule
     | alterMigrationRule
     | lockCluster
+    | unlockCluster
     ) SEMI?
     ;
diff --git a/distsql/parser/src/main/java/org/apache/shardingsphere/distsql/parser/core/kernel/KernelDistSQLStatementVisitor.java b/distsql/parser/src/main/java/org/apache/shardingsphere/distsql/parser/core/kernel/KernelDistSQLStatementVisitor.java
index 86385dd2e04..8415701d7fa 100644
--- a/distsql/parser/src/main/java/org/apache/shardingsphere/distsql/parser/core/kernel/KernelDistSQLStatementVisitor.java
+++ b/distsql/parser/src/main/java/org/apache/shardingsphere/distsql/parser/core/kernel/KernelDistSQLStatementVisitor.java
@@ -64,6 +64,7 @@ import org.apache.shardingsphere.distsql.parser.autogen.KernelDistSQLStatementPa
 import org.apache.shardingsphere.distsql.parser.autogen.KernelDistSQLStatementParser.StorageUnitDefinitionContext;
 import org.apache.shardingsphere.distsql.parser.autogen.KernelDistSQLStatementParser.StreamChannelContext;
 import org.apache.shardingsphere.distsql.parser.autogen.KernelDistSQLStatementParser.UnlabelComputeNodeContext;
+import org.apache.shardingsphere.distsql.parser.autogen.KernelDistSQLStatementParser.UnlockClusterContext;
 import org.apache.shardingsphere.distsql.parser.autogen.KernelDistSQLStatementParser.UnregisterStorageUnitContext;
 import org.apache.shardingsphere.distsql.parser.autogen.KernelDistSQLStatementParser.WorkerThreadContext;
 import org.apache.shardingsphere.distsql.parser.autogen.KernelDistSQLStatementParser.WriteDefinitionContext;
@@ -94,6 +95,7 @@ import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.RefreshT
 import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.SetDistVariableStatement;
 import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.SetInstanceStatusStatement;
 import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.UnlabelComputeNodeStatement;
+import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.UnlockClusterStatement;
 import org.apache.shardingsphere.distsql.parser.statement.rdl.alter.AlterStorageUnitStatement;
 import org.apache.shardingsphere.distsql.parser.statement.rdl.create.RegisterStorageUnitStatement;
 import org.apache.shardingsphere.distsql.parser.statement.rdl.create.SetDefaultSingleTableStorageUnitStatement;
@@ -359,11 +361,6 @@ public final class KernelDistSQLStatementVisitor extends KernelDistSQLStatementB
         return null == ctx ? null : (AlgorithmSegment) visit(ctx);
     }
     
-    @Override
-    public ASTNode visitLockCluster(final LockClusterContext ctx) {
-        return new LockClusterStatement((AlgorithmSegment) visitAlgorithmDefinition(ctx.lockStrategy().algorithmDefinition()));
-    }
-    
     @Override
     public ASTNode visitAlgorithmDefinition(final AlgorithmDefinitionContext ctx) {
         return new AlgorithmSegment(getIdentifierValue(ctx.algorithmTypeName()), buildProperties(ctx.propertiesDefinition()));
@@ -392,6 +389,16 @@ public final class KernelDistSQLStatementVisitor extends KernelDistSQLStatementB
         return null == ctx ? null : Integer.parseInt(ctx.intValue().getText());
     }
     
+    @Override
+    public ASTNode visitLockCluster(final LockClusterContext ctx) {
+        return new LockClusterStatement((AlgorithmSegment) visitAlgorithmDefinition(ctx.lockStrategy().algorithmDefinition()));
+    }
+    
+    @Override
+    public ASTNode visitUnlockCluster(final UnlockClusterContext ctx) {
+        return new UnlockClusterStatement();
+    }
+    
     @Override
     public ASTNode visitRateLimiter(final RateLimiterContext ctx) {
         return visit(ctx.algorithmDefinition());
diff --git a/distsql/statement/src/main/java/org/apache/shardingsphere/distsql/parser/statement/ral/updatable/UnlockClusterStatement.java b/distsql/statement/src/main/java/org/apache/shardingsphere/distsql/parser/statement/ral/updatable/UnlockClusterStatement.java
new file mode 100644
index 00000000000..5caad8d5b07
--- /dev/null
+++ b/distsql/statement/src/main/java/org/apache/shardingsphere/distsql/parser/statement/ral/updatable/UnlockClusterStatement.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.distsql.parser.statement.ral.updatable;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.distsql.parser.statement.ral.UpdatableRALStatement;
+
+/**
+ * Unlock cluster statement.
+ */
+@RequiredArgsConstructor
+public final class UnlockClusterStatement extends UpdatableRALStatement {
+}
diff --git a/infra/common/src/test/java/org/apache/shardingsphere/infra/state/ClusterStateContextTest.java b/infra/common/src/test/java/org/apache/shardingsphere/infra/state/ClusterStateContextTest.java
new file mode 100644
index 00000000000..a2b3b811af4
--- /dev/null
+++ b/infra/common/src/test/java/org/apache/shardingsphere/infra/state/ClusterStateContextTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.infra.state;
+
+import org.apache.shardingsphere.infra.state.cluster.ClusterState;
+import org.apache.shardingsphere.infra.state.cluster.ClusterStateContext;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public final class ClusterStateContextTest {
+    
+    private final ClusterStateContext clusterStateContext = new ClusterStateContext();
+    
+    @Test
+    public void assertSwitchStateWithUnavailable() {
+        clusterStateContext.switchState(ClusterState.UNAVAILABLE);
+        assertThat(clusterStateContext.getCurrentState(), is(ClusterState.UNAVAILABLE));
+        clusterStateContext.switchState(ClusterState.OK);
+    }
+    
+    @Test
+    public void assertSwitchStateWithReadOnly() {
+        clusterStateContext.switchState(ClusterState.READ_ONLY);
+        assertThat(clusterStateContext.getCurrentState(), is(ClusterState.READ_ONLY));
+        clusterStateContext.switchState(ClusterState.OK);
+    }
+    
+    @Test
+    public void assertSwitchStateWithMultiStateChange() {
+        clusterStateContext.switchState(ClusterState.UNAVAILABLE);
+        assertThrows(IllegalStateException.class, () -> clusterStateContext.switchState(ClusterState.READ_ONLY));
+        clusterStateContext.switchState(ClusterState.OK);
+        clusterStateContext.switchState(ClusterState.READ_ONLY);
+        assertThat(clusterStateContext.getCurrentState(), is(ClusterState.READ_ONLY));
+        clusterStateContext.switchState(ClusterState.OK);
+    }
+}
diff --git a/jdbc/core/src/test/java/org/apache/shardingsphere/driver/jdbc/core/datasource/ShardingSphereDataSourceTest.java b/jdbc/core/src/test/java/org/apache/shardingsphere/driver/jdbc/core/datasource/ShardingSphereDataSourceTest.java
index b1304c9e879..f76bd390a69 100644
--- a/jdbc/core/src/test/java/org/apache/shardingsphere/driver/jdbc/core/datasource/ShardingSphereDataSourceTest.java
+++ b/jdbc/core/src/test/java/org/apache/shardingsphere/driver/jdbc/core/datasource/ShardingSphereDataSourceTest.java
@@ -23,6 +23,7 @@ import org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConne
 import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
 import org.apache.shardingsphere.infra.database.DefaultDatabase;
 import org.apache.shardingsphere.infra.executor.sql.execute.engine.ConnectionMode;
+import org.apache.shardingsphere.infra.state.cluster.ClusterState;
 import org.apache.shardingsphere.infra.state.instance.InstanceState;
 import org.apache.shardingsphere.mode.manager.ContextManager;
 import org.apache.shardingsphere.parser.config.SQLParserRuleConfiguration;
@@ -62,6 +63,7 @@ public final class ShardingSphereDataSourceTest {
         ShardingSphereDataSource actual = new ShardingSphereDataSource(DefaultDatabase.LOGIC_NAME, null);
         ContextManager contextManager = getContextManager(actual);
         assertNotNull(contextManager.getMetaDataContexts().getMetaData().getDatabase(DefaultDatabase.LOGIC_NAME));
+        assertThat(contextManager.getClusterStateContext().getCurrentState(), is(ClusterState.OK));
         assertThat(contextManager.getInstanceContext().getInstance().getState().getCurrentState(), is(InstanceState.OK));
         assertTrue(contextManager.getDataSourceMap(DefaultDatabase.LOGIC_NAME).isEmpty());
     }
@@ -73,6 +75,7 @@ public final class ShardingSphereDataSourceTest {
         ShardingSphereDataSource actual = createShardingSphereDataSource(new MockedDataSource(connection));
         ContextManager contextManager = getContextManager(actual);
         assertNotNull(contextManager.getMetaDataContexts().getMetaData().getDatabase(DefaultDatabase.LOGIC_NAME));
+        assertThat(contextManager.getClusterStateContext().getCurrentState(), is(ClusterState.OK));
         assertThat(contextManager.getInstanceContext().getInstance().getState().getCurrentState(), is(InstanceState.OK));
         assertThat(contextManager.getDataSourceMap(DefaultDatabase.LOGIC_NAME).size(), is(1));
         assertThat(contextManager.getDataSourceMap(DefaultDatabase.LOGIC_NAME).get("ds").getConnection().getMetaData().getURL(), is("jdbc:mock://127.0.0.1/foo_ds"));
diff --git a/jdbc/core/src/test/java/org/apache/shardingsphere/driver/state/DriverStateContextTest.java b/jdbc/core/src/test/java/org/apache/shardingsphere/driver/state/DriverStateContextTest.java
index 8505665b1a2..b1c5e6b3b5b 100644
--- a/jdbc/core/src/test/java/org/apache/shardingsphere/driver/state/DriverStateContextTest.java
+++ b/jdbc/core/src/test/java/org/apache/shardingsphere/driver/state/DriverStateContextTest.java
@@ -25,6 +25,7 @@ import org.apache.shardingsphere.infra.database.type.dialect.MySQLDatabaseType;
 import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
 import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import org.apache.shardingsphere.infra.metadata.database.rule.ShardingSphereRuleMetaData;
+import org.apache.shardingsphere.infra.state.cluster.ClusterStateContext;
 import org.apache.shardingsphere.infra.state.instance.InstanceStateContext;
 import org.apache.shardingsphere.mode.manager.ContextManager;
 import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
@@ -66,6 +67,7 @@ public final class DriverStateContextTest {
                 mock(MetaDataPersistService.class), new ShardingSphereMetaData(databases, globalRuleMetaData, new ConfigurationProperties(new Properties())));
         when(contextManager.getMetaDataContexts()).thenReturn(metaDataContexts);
         when(contextManager.getInstanceContext().getInstance().getState()).thenReturn(new InstanceStateContext());
+        when(contextManager.getClusterStateContext()).thenReturn(new ClusterStateContext());
     }
     
     private Map<String, ShardingSphereDatabase> mockDatabases() {
diff --git a/mode/core/src/main/java/org/apache/shardingsphere/mode/manager/ContextManager.java b/mode/core/src/main/java/org/apache/shardingsphere/mode/manager/ContextManager.java
index 547f2631bd8..9d141a169b3 100644
--- a/mode/core/src/main/java/org/apache/shardingsphere/mode/manager/ContextManager.java
+++ b/mode/core/src/main/java/org/apache/shardingsphere/mode/manager/ContextManager.java
@@ -94,7 +94,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Renew meta data contexts.
-     *
+     * 
      * @param metaDataContexts meta data contexts
      */
     public synchronized void renewMetaDataContexts(final MetaDataContexts metaDataContexts) {
@@ -103,7 +103,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Get data source map.
-     *
+     * 
      * @param databaseName database name
      * @return data source map
      */
@@ -113,7 +113,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Add database.
-     *
+     * 
      * @param databaseName database name
      */
     public synchronized void addDatabase(final String databaseName) {
@@ -126,7 +126,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Drop database.
-     *
+     * 
      * @param databaseName database name
      */
     public synchronized void dropDatabase(final String databaseName) {
@@ -139,7 +139,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Add schema.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      */
@@ -152,7 +152,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Drop schema.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      */
@@ -165,7 +165,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter schema.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      * @param toBeDeletedTableName to be deleted table name
@@ -178,7 +178,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter schema.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      * @param toBeChangedTable to be changed table
@@ -227,7 +227,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter rule configuration.
-     *
+     * 
      * @param databaseName database name
      * @param ruleConfigs rule configurations
      */
@@ -247,7 +247,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter schema meta data.
-     *
+     * 
      * @param databaseName database name
      * @param reloadDatabase reload database
      * @param currentDatabase current database
@@ -261,7 +261,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter data source configuration.
-     *
+     * 
      * @param databaseName database name
      * @param dataSourcePropsMap altered data source properties map
      */
@@ -291,7 +291,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Renew ShardingSphere databases.
-     *
+     * 
      * @param database database
      * @param resource resource
      * @return ShardingSphere databases
@@ -307,7 +307,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter data source and rule configuration.
-     *
+     * 
      * @param databaseName database name
      * @param dataSourcePropsMap data source props map
      * @param ruleConfigs rule configurations
@@ -337,7 +337,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Create meta data contexts.
-     *
+     * 
      * @param databaseName database name
      * @param switchingResource switching resource
      * @param ruleConfigs rule configs
@@ -364,7 +364,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Create changed databases.
-     *
+     * 
      * @param databaseName database name
      * @param switchingResource switching resource
      * @param ruleConfigs rule configs
@@ -402,7 +402,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Create new ShardingSphere database.
-     *
+     * 
      * @param originalDatabase original database
      * @return ShardingSphere databases
      */
@@ -414,7 +414,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter global rule configuration.
-     *
+     * 
      * @param ruleConfigs global rule configuration
      */
     @SuppressWarnings("rawtypes")
@@ -433,7 +433,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter properties.
-     *
+     * 
      * @param props properties to be altered
      */
     public synchronized void alterProperties(final Properties props) {
@@ -444,7 +444,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Reload database meta data from governance center.
-     *
+     * 
      * @param databaseName to be reloaded database name
      */
     public synchronized void reloadDatabaseMetaData(final String databaseName) {
@@ -466,7 +466,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Delete schema names.
-     *
+     * 
      * @param databaseName database name
      * @param reloadDatabase reload database
      * @param currentDatabase current database
@@ -478,7 +478,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Reload schema.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName to be reloaded schema name
      * @param dataSourceName data source name
@@ -511,7 +511,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Reload table.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      * @param tableName to be reloaded table name
@@ -527,7 +527,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Reload table from single data source.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      * @param dataSourceName data source name
@@ -570,6 +570,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Drop ShardingSphere data database.
+     * 
      * @param databaseName database name
      */
     public synchronized void dropShardingSphereDatabaseData(final String databaseName) {
@@ -581,7 +582,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Add ShardingSphere schema data.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      */
@@ -594,7 +595,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Drop ShardingSphere schema data.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      */
@@ -608,7 +609,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Add ShardingSphere table data.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      * @param tableName table name
@@ -625,7 +626,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Drop ShardingSphere table data.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      * @param tableName table name
@@ -639,7 +640,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Alter ShardingSphere row data.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      * @param tableName table name
@@ -661,7 +662,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Delete ShardingSphere row data.
-     *
+     * 
      * @param databaseName database name
      * @param schemaName schema name
      * @param tableName table name
@@ -677,7 +678,7 @@ public final class ContextManager implements AutoCloseable {
     
     /**
      * Update cluster state.
-     *
+     * 
      * @param status status
      */
     public void updateClusterState(final String status) {
diff --git a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/updatable/UnlockClusterUpdater.java b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/updatable/UnlockClusterUpdater.java
new file mode 100644
index 00000000000..be35585ffe8
--- /dev/null
+++ b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/updatable/UnlockClusterUpdater.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.proxy.backend.handler.distsql.ral.updatable;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.distsql.handler.ral.update.RALUpdater;
+import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.UnlockClusterStatement;
+import org.apache.shardingsphere.infra.lock.LockContext;
+import org.apache.shardingsphere.infra.state.cluster.ClusterState;
+import org.apache.shardingsphere.infra.util.exception.ShardingSpherePreconditions;
+import org.apache.shardingsphere.infra.util.exception.external.sql.type.generic.UnsupportedSQLOperationException;
+import org.apache.shardingsphere.mode.lock.GlobalLockDefinition;
+import org.apache.shardingsphere.mode.manager.ContextManager;
+import org.apache.shardingsphere.mode.manager.cluster.coordinator.registry.status.cluster.event.ClusterStatusChangedEvent;
+import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
+
+/**
+ * Unlock cluster updater.
+ */
+@RequiredArgsConstructor
+public final class UnlockClusterUpdater implements RALUpdater<UnlockClusterStatement> {
+    
+    @Override
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public void executeUpdate(final String databaseName, final UnlockClusterStatement sqlStatement) {
+        checkMode();
+        checkState();
+        ContextManager contextManager = ProxyContext.getInstance().getContextManager();
+        LockContext lockContext = contextManager.getInstanceContext().getLockContext();
+        lockContext.unlock(new GlobalLockDefinition("cluster_lock"));
+        contextManager.getInstanceContext().getEventBusContext().post(new ClusterStatusChangedEvent(ClusterState.OK));
+        // TODO unlock snapshot info if locked
+    }
+    
+    private void checkMode() {
+        ShardingSpherePreconditions.checkState(ProxyContext.getInstance().getContextManager().getInstanceContext().isCluster(),
+                () -> new UnsupportedSQLOperationException("Only allowed in cluster mode"));
+    }
+    
+    private void checkState() {
+        ClusterState currentState = ProxyContext.getInstance().getContextManager().getClusterStateContext().getCurrentState();
+        ShardingSpherePreconditions.checkState(ClusterState.OK != currentState, () -> new IllegalStateException("Cluster is not locked"));
+    }
+    
+    @Override
+    public String getType() {
+        return UnlockClusterStatement.class.getName();
+    }
+}
diff --git a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/lock/impl/ClusterWriteLockStrategy.java b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/lock/impl/ClusterWriteLockStrategy.java
index 94a2d1b5f8f..d6974b366da 100644
--- a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/lock/impl/ClusterWriteLockStrategy.java
+++ b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/lock/impl/ClusterWriteLockStrategy.java
@@ -37,7 +37,7 @@ public class ClusterWriteLockStrategy implements ClusterLockStrategy {
         LockContext lockContext = contextManager.getInstanceContext().getLockContext();
         if (lockContext.tryLock(new GlobalLockDefinition("cluster_lock"), -1)) {
             contextManager.getInstanceContext().getEventBusContext().post(new ClusterStatusChangedEvent(ClusterState.READ_ONLY));
-            // TODO lock csn
+            // TODO lock snapshot info
         }
     }
     
diff --git a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/state/impl/ReadOnlyProxyState.java b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/state/impl/ReadOnlyProxyState.java
index d0e7d6f0bb4..285e21a4039 100644
--- a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/state/impl/ReadOnlyProxyState.java
+++ b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/state/impl/ReadOnlyProxyState.java
@@ -18,6 +18,7 @@
 package org.apache.shardingsphere.proxy.backend.state.impl;
 
 import org.apache.shardingsphere.distsql.parser.statement.ral.UpdatableRALStatement;
+import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.UnlockClusterStatement;
 import org.apache.shardingsphere.distsql.parser.statement.rdl.RDLStatement;
 import org.apache.shardingsphere.proxy.backend.exception.ReadOnlyException;
 import org.apache.shardingsphere.proxy.backend.state.spi.ProxyClusterState;
@@ -41,7 +42,7 @@ public final class ReadOnlyProxyState implements ProxyClusterState {
     
     private boolean isUnsupportedStatement(final SQLStatement sqlStatement) {
         return sqlStatement instanceof InsertStatement || sqlStatement instanceof UpdateStatement || sqlStatement instanceof DeleteStatement || sqlStatement instanceof DDLStatement
-                || sqlStatement instanceof UpdatableRALStatement || sqlStatement instanceof RDLStatement;
+                || sqlStatement instanceof UpdatableRALStatement && !(sqlStatement instanceof UnlockClusterStatement) || sqlStatement instanceof RDLStatement;
     }
     
     @Override
diff --git a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/state/impl/UnavailableProxyState.java b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/state/impl/UnavailableProxyState.java
index 9ec5f46ddd2..fdc55fdbcb6 100644
--- a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/state/impl/UnavailableProxyState.java
+++ b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/state/impl/UnavailableProxyState.java
@@ -19,6 +19,7 @@ package org.apache.shardingsphere.proxy.backend.state.impl;
 
 import org.apache.shardingsphere.distsql.parser.statement.ral.QueryableRALStatement;
 import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.ImportMetaDataStatement;
+import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.UnlockClusterStatement;
 import org.apache.shardingsphere.distsql.parser.statement.rql.RQLStatement;
 import org.apache.shardingsphere.proxy.backend.exception.UnavailableException;
 import org.apache.shardingsphere.proxy.backend.state.spi.ProxyClusterState;
@@ -42,7 +43,7 @@ public final class UnavailableProxyState implements ProxyClusterState {
     
     private boolean isSupportedStatement(final SQLStatement sqlStatement) {
         return sqlStatement instanceof ImportMetaDataStatement || sqlStatement instanceof ShowStatement || sqlStatement instanceof QueryableRALStatement || sqlStatement instanceof RQLStatement
-                || sqlStatement instanceof MySQLShowDatabasesStatement || sqlStatement instanceof MySQLUseStatement;
+                || sqlStatement instanceof UnlockClusterStatement || sqlStatement instanceof MySQLShowDatabasesStatement || sqlStatement instanceof MySQLUseStatement;
     }
     
     @Override
diff --git a/proxy/backend/core/src/main/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.ral.update.RALUpdater b/proxy/backend/core/src/main/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.ral.update.RALUpdater
index 44eecacfe90..a0fccd573c7 100644
--- a/proxy/backend/core/src/main/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.ral.update.RALUpdater
+++ b/proxy/backend/core/src/main/resources/META-INF/services/org.apache.shardingsphere.distsql.handler.ral.update.RALUpdater
@@ -25,4 +25,5 @@ org.apache.shardingsphere.proxy.backend.handler.distsql.ral.updatable.AlterReadw
 org.apache.shardingsphere.proxy.backend.handler.distsql.ral.updatable.SetDistVariableUpdater
 org.apache.shardingsphere.proxy.backend.handler.distsql.ral.updatable.RefreshDatabaseMetaDataUpdater
 org.apache.shardingsphere.proxy.backend.handler.distsql.ral.updatable.LockClusterUpdater
+org.apache.shardingsphere.proxy.backend.handler.distsql.ral.updatable.UnlockClusterUpdater
 org.apache.shardingsphere.proxy.backend.handler.distsql.ral.updatable.RefreshTableMetaDataUpdater
diff --git a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/context/ProxyContextTest.java b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/context/ProxyContextTest.java
index 9778c7d0998..6b743a997ff 100644
--- a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/context/ProxyContextTest.java
+++ b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/context/ProxyContextTest.java
@@ -24,6 +24,7 @@ import org.apache.shardingsphere.infra.instance.InstanceContext;
 import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
 import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import org.apache.shardingsphere.infra.metadata.database.rule.ShardingSphereRuleMetaData;
+import org.apache.shardingsphere.infra.state.cluster.ClusterState;
 import org.apache.shardingsphere.mode.manager.ContextManager;
 import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
 import org.apache.shardingsphere.mode.metadata.persist.MetaDataPersistService;
@@ -67,6 +68,8 @@ public final class ProxyContextTest {
     public void assertInit() {
         MetaDataContexts metaDataContexts = new MetaDataContexts(mock(MetaDataPersistService.class), new ShardingSphereMetaData());
         ProxyContext.init(new ContextManager(metaDataContexts, mock(InstanceContext.class, RETURNS_DEEP_STUBS)));
+        assertThat(ProxyContext.getInstance().getContextManager().getClusterStateContext(), is(ProxyContext.getInstance().getContextManager().getClusterStateContext()));
+        assertThat(ProxyContext.getInstance().getContextManager().getClusterStateContext().getCurrentState(), is(ClusterState.OK));
         assertThat(ProxyContext.getInstance().getContextManager().getMetaDataContexts(), is(ProxyContext.getInstance().getContextManager().getMetaDataContexts()));
         assertTrue(ProxyContext.getInstance().getInstanceStateContext().isPresent());
         assertThat(ProxyContext.getInstance().getInstanceStateContext(), is(ProxyContext.getInstance().getInstanceStateContext()));
diff --git a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/updatable/UnlockClusterUpdaterTest.java b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/updatable/UnlockClusterUpdaterTest.java
new file mode 100644
index 00000000000..10eb288b1ea
--- /dev/null
+++ b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/distsql/ral/updatable/UnlockClusterUpdaterTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.proxy.backend.handler.distsql.ral.updatable;
+
+import org.apache.shardingsphere.distsql.parser.statement.ral.updatable.UnlockClusterStatement;
+import org.apache.shardingsphere.infra.state.cluster.ClusterState;
+import org.apache.shardingsphere.infra.util.exception.external.sql.type.generic.UnsupportedSQLOperationException;
+import org.apache.shardingsphere.mode.manager.ContextManager;
+import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
+import org.apache.shardingsphere.test.mock.AutoMockExtension;
+import org.apache.shardingsphere.test.mock.StaticMockSettings;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(AutoMockExtension.class)
+@StaticMockSettings(ProxyContext.class)
+public final class UnlockClusterUpdaterTest {
+    
+    @Test
+    public void assertExecuteWithNotClusterMode() {
+        ContextManager contextManager = mock(ContextManager.class, RETURNS_DEEP_STUBS);
+        when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+        UnlockClusterUpdater updater = new UnlockClusterUpdater();
+        assertThrows(UnsupportedSQLOperationException.class, () -> updater.executeUpdate("foo", new UnlockClusterStatement()));
+    }
+    
+    @Test
+    public void assertExecuteWithNotLockedCluster() {
+        ContextManager contextManager = mock(ContextManager.class, RETURNS_DEEP_STUBS);
+        when(contextManager.getInstanceContext().isCluster()).thenReturn(true);
+        when(contextManager.getClusterStateContext().getCurrentState()).thenReturn(ClusterState.OK);
+        when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+        UnlockClusterUpdater updater = new UnlockClusterUpdater();
+        assertThrows(IllegalStateException.class, () -> updater.executeUpdate("foo", new UnlockClusterStatement()));
+    }
+}