diff --git a/extra/exports b/extra/exports
index 10a7fc52b30d859fd0a1a12f52a6c21c65d59059..d679db1bfdc8aef0d11970ef7a6f57c24b3e3e8c 100644
--- a/extra/exports
+++ b/extra/exports
@@ -595,7 +595,7 @@ obuf_create
 obuf_destroy
 sql_prepare_ext
 sql_stmt_finalize
-sql_unprepare
+sql_unprepare_ext
 sql_stmt_query_str
 sql_stmt_calculate_id
 sql_execute_prepared_ext
diff --git a/src/box/execute.c b/src/box/execute.c
index 60f5a667d3e7ad721c8abe27ade8dceceb8f4f6c..f711cde40640c7686272c7fdd033003b0b5fe19a 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -171,14 +171,32 @@ sql_prepare(const char *sql, int len, struct port *port)
 
 /**
  * Find or create prepared statement by its SQL query.
- * Returns compiled statement ID.
+ * Returns compiled statement ID and session ID.
  */
 int
-sql_prepare_ext(const char *sql, int len, uint32_t *stmt_id)
+sql_prepare_ext(const char *sql, int len, uint32_t *stmt_id, uint64_t *session_id)
 {
 	struct sql_stmt *stmt = NULL;
 	if (sql_stmt_find_or_create(sql, len, stmt_id, &stmt) != 0)
 		return -1;
+	*session_id = current_session()->id;
+	return 0;
+}
+
+int
+sql_unprepare_ext(uint32_t stmt_id, uint64_t session_id)
+{
+	struct session *session = session_find(session_id);
+	if (session == NULL) {
+		diag_set(ClientError, ER_NO_SUCH_SESSION, session_id);
+		return -1;
+	}
+	if (!session_check_stmt_id(session, stmt_id)) {
+		diag_set(ClientError, ER_WRONG_QUERY_ID, stmt_id);
+		return -1;
+	}
+	session_remove_stmt_id(session, stmt_id);
+	sql_stmt_unref(stmt_id);
 	return 0;
 }
 
diff --git a/src/box/execute.h b/src/box/execute.h
index e1089b987e88d5cc1c05123ab43acf71dc8bcf5f..d9ba694bbcec134a076ba16f501c5a33d5735f81 100644
--- a/src/box/execute.h
+++ b/src/box/execute.h
@@ -136,15 +136,26 @@ sql_stmt_busy(const struct sql_stmt *stmt);
 int
 sql_prepare(const char *sql, int len, struct port *port);
 
+/**
+ * Unprepare statement from the session (exported version).
+ *
+ * @param stmt_id ID of prepared stmt.
+ * @param sid session ID.
+ */
+int
+sql_unprepare_ext(uint32_t stmt_id, uint64_t sid);
+
 /**
  * Prepare statement (exported version).
  *
  * @param sql UTF-8 encoded SQL query.
  * @param len Length of @param sql in bytes.
  * @param[out] stmt_id Prepared statement ID.
+ * @param[out] session_id session ID.
  */
 int
-sql_prepare_ext(const char *sql, int len, uint32_t *stmt_id);
+sql_prepare_ext(const char *sql, int len, uint32_t *stmt_id,
+		uint64_t *session_id);
 
 #if defined(__cplusplus)
 } /* extern "C" { */
diff --git a/test/sql-luatest/sql_api_test.lua b/test/sql-luatest/sql_api_test.lua
index f85fb2a444bd91576d498095865004c9a36a8e84..7134325e5bfa253e4c235c6012292bac3cffa451 100644
--- a/test/sql-luatest/sql_api_test.lua
+++ b/test/sql-luatest/sql_api_test.lua
@@ -38,8 +38,10 @@ g.before_all(function()
             uint32_t stmt_id, const char *mp_params,
             uint64_t vdbe_max_steps, struct obuf *out_buf);
 
-        int sql_prepare_ext(const char *sql, int len, uint32_t *stmt_id);
-
+        int sql_prepare_ext(
+            const char *sql, int len,
+            uint32_t *stmt_id, uint64_t *sid);
+        int sql_unprepare_ext(uint32_t stmt_id, uint64_t sid);
     ]]
     g.server = server:new({alias = 'sql_api'})
     g.server:start()
@@ -159,13 +161,92 @@ g.test_stmt_prepare = function()
 
         local ffi = require('ffi')
         local stmt_id = ffi.new('uint32_t[1]')
+        local session_id = ffi.new('uint64_t[1]')
+
+        -- Prepare the statement.
+        res = ffi.C.sql_prepare_ext('VALUES (?)', 10, stmt_id, session_id)
+        t.assert_equals(res, 0)
+
+        -- Check the prepared statement.
+        res = box.execute(tonumber(stmt_id[0]), {'ABC'})
+        t.assert_equals(res.rows[1][1], 'ABC')
+
+        -- Unprepare the statement
+        res = ffi.C.sql_unprepare_ext(
+            tonumber(stmt_id[0]), tonumber(session_id[0]))
+        t.assert_equals(res, 0)
+
+        -- Calling unprepare again returns error
+        res = ffi.C.sql_unprepare_ext(
+            tonumber(stmt_id[0]), tonumber(session_id[0]))
+        t.assert_not_equals(res, 0)
+        local err = tostring(box.error.last())
+        local s = string.format(
+            "Prepared statement with id %u does not exist",
+            tonumber(stmt_id[0]))
+        t.assert_str_contains(err, s)
+
+        -- Check unprepare from invalid session
+        -- returns error
+        local wrong_sid = tonumber(session_id[0]) + 1
+        res = ffi.C.sql_unprepare_ext(tonumber(stmt_id[0]), wrong_sid)
+        t.assert_not_equals(res, 0)
+        err = tostring(box.error.last())
+        s = string.format("Session %u does not exist", wrong_sid)
+        t.assert_str_contains(err, s)
+
+        -- Check statement was deleted
+        res = box.execute(tonumber(stmt_id[0]), {'ABC'})
+        t.assert_not_equals(res, 0)
+    end)
+end
+
+g.test_unprepare_from_other_session = function()
+    g.server:exec(function()
+        local fiber = require('fiber')
+
+        local res, err, ok
+
+        local ffi = require('ffi')
+        local stmt_id = ffi.new('uint32_t[1]')
+        local session_id = ffi.new('uint64_t[1]')
 
         -- Prepare the statement.
-        res = ffi.C.sql_prepare_ext('VALUES (?)', 10, stmt_id)
+        res = ffi.C.sql_prepare_ext('VALUES (?)', 10, stmt_id, session_id)
         t.assert_equals(res, 0)
 
         -- Check the prepared statement.
         res = box.execute(tonumber(stmt_id[0]), {'ABC'})
         t.assert_equals(res.rows[1][1], 'ABC')
+
+        local new_session = function(stmt_id, session_id)
+            local ffi = require('ffi')
+            local id = tonumber(stmt_id[0])
+            local sid = tonumber(session_id[0])
+
+            -- Unprepare the statement
+            res = ffi.C.sql_unprepare_ext(id, sid)
+            t.assert_equals(res, 0)
+
+            -- Check we get an error when trying to unprepare
+            -- invalid statement from other session
+            res = ffi.C.sql_unprepare_ext(id, sid)
+            err = tostring(box.error.last())
+            local s = string.format(
+                "Prepared statement with id %u does not exist", id)
+            t.assert_str_contains(err, s)
+        end
+
+        local f = fiber.new(new_session, stmt_id, session_id)
+        f:set_joinable(true)
+        ok, res = f:join()
+
+        if not ok then
+            error(res:unpack())
+        end
+
+        -- Check statement was deleted
+        res = box.execute(tonumber(stmt_id[0]), {'ABC'})
+        t.assert_not_equals(res, 0)
     end)
 end