Использование нескольких активных результирующих наборов (MARS) в собственном клиенте SQL Server

Применимо к:SQL ServerAzure SQL DatabaseAzure Managed InstanceAzure Synapse Analytics AnalyticsPlatform System (PDW)

Внимание

Собственный клиент SQL Server (часто сокращенный SNAC) был удален из SQL Server 2022 (16.x) и SQL Server Management Studio 19 (SSMS). Собственный клиент SQL Server (SQLNCLI или SQLNCLI11) и устаревший поставщик Microsoft OLE DB для SQL Server (SQLOLEDB) не рекомендуется для разработки новых приложений. Перейдите на новый драйвер Microsoft OLE DB (MSOLEDBSQL) для SQL Server или последний драйвер Microsoft ODBC для SQL Server . Сведения о SQLNCLI, которые поставляется в качестве компонента ядра СУБД SQL Server (версии 2012–2019), см. в этом исключении жизненного цикла поддержки.

SQL Server 2005 (9.x) представила поддержку нескольких активных результирующих наборов (MARS) в приложениях, обращаюющихся к ядро СУБД. В более ранних версиях SQL Server приложения базы данных не могут поддерживать несколько активных инструкций в соединении. При использовании результирующих наборов SQL Server по умолчанию приложение пришлось обрабатывать или отменять все результирующие наборы из одного пакета, прежде чем выполнять любой другой пакет в этом подключении. В SQL Server 2005 (9.x) появился новый атрибут подключения, позволяющий приложениям иметь несколько ожидающих запросов на подключение, а в частности, иметь несколько активных результирующих наборов по умолчанию для каждого подключения.

Режим MARS упрощает проектирование приложений за счет использования следующих функций.

  • У приложений может быть открыто несколько применяемых по умолчанию результирующих наборов; при этом приложения могут по очереди считывать из них данные.

  • Когда применяемые по умолчанию результирующие наборы открыты, приложения могут выполнять другие инструкции (например: INSERT, UPDATE, DELETE и вызовы хранимых процедур).

При работе с приложениями, предусматривающими функционирование в режиме MARS, полезно руководствоваться следующими рекомендациями.

  • Результирующие наборы по умолчанию следует использовать с имеющими небольшой период жизни или с короткими результирующими наборами, сформированными при помощи одной инструкции SQL (SELECT, DML with OUTPUT, RECEIVE, READ TEXT и т. д.).

  • Серверные курсоры нужно использовать с имеющими более длительный период жизни или с более крупными результирующими наборами, сформированными при помощи одной инструкции SQL.

  • Результаты нужно всегда прочитывать до конца на предмет наличия в них процедурных запросов, вне зависимости от того, возвращают ли они результаты, и на предмет наличия в них пакетов, возвращающих несколько результатов.

  • По возможности для изменения свойств соединений и для управления транзакциями следует использовать не инструкции Transact-SQL, а вызовы API.

  • При работе в режиме MARS в ситуациях, когда выполняются параллельные пакеты, ограниченные областью сеанса олицетворения не допускаются.

Заметка

По умолчанию функция MARS не включена драйвером. Чтобы использовать MARS при подключении к SQL Server с собственным клиентом SQL Server, необходимо специально включить MARS в строке подключения. Однако некоторые приложения могут включить MARS по умолчанию, если приложение обнаруживает, что драйвер поддерживает MARS. Для этих приложений можно отключить MARS в строке подключения по мере необходимости. Дополнительные сведения см. в разделах о поставщике OLE DB собственного клиента SQL Server и драйверах ODBC собственного клиента SQL Server.

Sql Server Native Client не ограничивает количество активных инструкций в соединении.

Типичные приложения, которые не должны иметь более одной пакетной или хранимой процедуры с несколькими операторами, выполняющимися одновременно, выиграют от MARS, не имея необходимости понимать, как выполняется MARS. Однако приложения с более сложными требованиями должны принимать это во внимание.

Режим MARS дает возможность поочередно выполнять несколько запросов с использованием одного соединения. Иначе говоря, существует возможность выполнения пакета с одновременным выполнением других запросов. Впрочем, надо отметить, что режим MARS определяется в терминах чередования, а не в терминах параллельного выполнения.

Инфраструктура режима MARS предоставляет возможность поочередного выполнения нескольких пакетов, хотя выполнение может переключаться лишь в четко определенных пунктах. Кроме того, почти все инструкции должны выполняться атомарным образом внутри пакета. Операторы, возвращающие строки клиенту, которые иногда называются точками доходности, могут пересекать выполнение до завершения, пока строки отправляются клиенту, например:

  • ВЫБРАТЬ

  • FETCH

  • ПРИЕМ

Все иные инструкции, выполняемые как часть хранимой процедуры или пакета, должны выполняться до конца, и только после этого выполнение может быть передано другим запросам MARS.

Точный порядок чередования выполнения пакетов определяется рядом факторов, поэтому предугадать точную последовательность выполнения содержащих точки выхода команд из нескольких пакетов затруднительно. Необходимо проявлять осторожность, чтобы избегать нежелательных побочных эффектов, которые вызываются поочередным выполнением подобных сложных пакетов.

Вы сможете избежать проблем, если при управлении состоянием соединений (SET, USE) и транзакциями (BEGIN TRAN, COMMIT, ROLLBACK) будете использовать вызовы API, а не инструкции Transact-SQL, если не будете включать эти инструкции в пакеты из нескольких инструкций, также содержащие точки выхода, и если будете сериализовать выполнение таких пакетов посредством использования либо отмены всех результатов.

Заметка

Пакет хранимых процедур, начинающий ручную или неявную транзакцию с активированным режимом MARS, должен завершать транзакцию до выхода пакета. Если это не так, SQL Server откатит все изменения, внесенные транзакцией после завершения пакета. Такая транзакция управляется SQL Server как транзакция с пакетной областью. Это новый тип транзакций, появившихся в SQL Server 2005 (9.x), чтобы обеспечить использование существующих хорошо функционируют хранимых процедур при включении MARS. Дополнительные сведения о транзакциях контекста пакета см. в статье Инструкции Transact-SQL.

Пример использования MARS из ADO см. в статье Using ADO with SQL Server Native Client.

Выполняющаяся в памяти OLTP

Выполняющаяся в памяти OLTP поддерживает режим MARS с помощью запросов и скомпилированных в собственном коде хранимых процедур. Режим MARS позволяет запрашивать данные из нескольких запросов без необходимости полного извлечения каждого результирующего набора перед отправкой запроса для выборки строк из нового результирующего набора. Для успешного чтения из нескольких открытых результирующих наборов необходимо использовать подключение с поддержкой MARS.

Режим MARS отключен по умолчанию, поэтому его необходимо явно включить, добавив MultipleActiveResultSets=True в строку подключения. В следующем примере демонстрируется, как подключиться к экземпляру SQL Server, а также как указать, что режим MARS включен.

Data Source=MSSQL; Initial Catalog=AdventureWorks; Integrated Security=SSPI; MultipleActiveResultSets=True  

Режим MARS с выполняющейся в памяти OLTP, по сути, аналогичен режиму MARS в оставшейся части ядра SQL. Ниже перечислены различия при использовании режима MARS для таблиц, оптимизированных для памяти, и хранимых процедур, скомпилированных в собственном коде.

Режим MARS и таблицы, оптимизированные для памяти

Ниже приведены различия между таблицами, оптимизированными для дисков и памяти, при использовании подключения с включенным режимом MARS.

  • Две инструкции могут изменять данные в том же целевом объекте, но если они обе пытаются изменить одну и ту же запись, конфликт между двумя операциями приведет к сбою новой операции. Тем не менее если обе операции изменяют разные записи, операции будут выполнены.

  • Каждая инструкция выполняется с изоляцией моментального снимка, поэтому новые операции не могут видеть изменения, внесенные существующими инструкциями. Даже если параллельные инструкции выполняются в рамках одной транзакции, ядро SQL создает транзакции контекста пакета для каждой инструкции, изолированной друг от друга. Тем не менее транзакции контекста пакета по-прежнему связаны друг с другом, поэтому откат одной транзакции влияет на другие, которые находятся в том же пакете.

  • Операции DDL запрещены в пользовательских транзакциях, поэтому они будут немедленно завершаться сбоем.

Режим MARS и скомпилированные в собственном коде хранимые процедуры

Скомпилированные в собственном коде хранимые процедуры могут работать в подключениях с включенным режимом MARS и передавать выполнение другой инструкции только при обнаружении точки передачи. Для точки передачи требуется инструкция SELECT. Это единственная инструкция в скомпилированной в собственном коде хранимой процедуре, которая может передавать выполнение другой инструкции. Если инструкция SELECT отсутствует в процедуре, выполнение процедуры не передастся и она будет выполнена до начала других инструкций.

Режим MARS и транзакции выполняющейся в памяти OLTP

Изменения, внесенные чередующимися инструкциями и атомарными блоками, изолированы друг от друга. Например, если одна инструкция или атомарный блок вносит некоторые изменения, а затем передает выполнение другой инструкции, то новая инструкция не будет видеть изменения, внесенные первой инструкцией. Кроме того, когда первая инструкция возобновляет выполнение, она не увидит никаких изменений, внесенных другими инструкциями. Инструкции будут видеть только те изменения, которые были завершены и зафиксированы перед их запуском.

Новая транзакция пользователя может быть запущена в текущей транзакции пользователя с помощью инструкции BEGIN TRANSACTION. Это поддерживается только в режиме взаимодействия, поэтому инструкцию BEGIN TRANSACTION можно вызывать только из инструкции T-SQL, а не из скомпилированной в собственном коде хранимой процедуры. Вы можете создать точку сохранения в транзакции с помощью SAVE TRANSACTION или вызова API для транзакции. Сохранить (save_point_name) для отката в точку сохранения. Эта функция также доступна только из инструкций T-SQL, а не из хранимых процедур, скомпилированных в собственном коде.

Режим MARS и индексы columnstore

SQL Server (начиная с версии 2016) поддерживает режим MARS с индексами columnstore. SQL Server 2014 использует функцию MARS для соединения только для чтения с таблицами с индексом columnstore. Но SQL Server 2014 не поддерживает функцию MARS для параллельного выполнения операций DML в таблице с индексом columnstore. В этом случае SQL Server завершит подключения и прервет выполнение транзакций. В SQL Server 2012 используются индексы columnstore только для чтения и режим MARS к ним не применяется.

Поставщик OLE DB для собственного клиента SQL Server

Поставщик OLE DB собственного клиента SQL Server поддерживает MARS через добавление свойства инициализации источника данных SSPROP_INIT_MARSCONNECTION, которое реализуется в наборе свойств DBPROPSET_SQLSERVERDBINIT. Кроме того, добавлено новое ключевое слово для строки подключения — MarsConn. Оно принимает значения true и false. Значение по умолчанию — false.

Для свойства источника данных DBPROP_MULTIPLECONNECTIONS по умолчанию применяется значение VARIANT_TRUE. Это значит, что поставщик создаст несколько соединений для поддержки ряда параллельных объектов команд и наборов строк. При включении MARS собственный клиент SQL Server может поддерживать несколько объектов команд и наборов строк в одном соединении, поэтому MULTIPLE_CONNECTIONS по умолчанию VARIANT_FALSE.

Дополнительные сведения об улучшениях, появившихся в наборе свойств DBPROPSET_SQLSERVERDBINIT, см. в статье Initialization and Authorization Properties (Свойства инициализации и авторизации).

Пример поставщика OLE DB для собственного клиента SQL Server

В этом примере объект источника данных создается с помощью поставщика OLE DB SQL Server Native, а MARS включен с помощью набора свойств DBPROPSET_SQLSERVERDBINIT перед созданием объекта сеанса.

#include <sqlncli.h>  
  
IDBInitialize *pIDBInitialize = NULL;  
IDBCreateSession *pIDBCreateSession = NULL;  
IDBProperties *pIDBProperties = NULL;  
  
// Create the data source object.  
hr = CoCreateInstance(CLSID_SQLNCLI10, NULL,  
   CLSCTX_INPROC_SERVER,  
   IID_IDBInitialize,   
    (void**)&pIDBInitialize);  
  
hr = pIDBInitialize->QueryInterface(IID_IDBProperties, (void**)&pIDBProperties);  
  
// Set the MARS property.  
DBPROP rgPropMARS;  
  
// The following is necessary since MARS is off by default.  
rgPropMARS.dwPropertyID = SSPROP_INIT_MARSCONNECTION;  
rgPropMARS.dwOptions = DBPROPOPTIONS_REQUIRED;  
rgPropMARS.dwStatus = DBPROPSTATUS_OK;  
rgPropMARS.colid = DB_NULLID;  
V_VT(&(rgPropMARS.vValue)) = VT_BOOL;  
V_BOOL(&(rgPropMARS.vValue)) = VARIANT_TRUE;  
  
// Create the structure containing the properties.  
DBPROPSET PropSet;  
PropSet.rgProperties = &rgPropMARS;  
PropSet.cProperties = 1;  
PropSet.guidPropertySet = DBPROPSET_SQLSERVERDBINIT;  
  
// Get an IDBProperties pointer and set the initialization properties.  
pIDBProperties->SetProperties(1, &PropSet);  
pIDBProperties->Release();  
  
// Initialize the data source object.  
hr = pIDBInitialize->Initialize();  
  
//Create a session object from a data source object.  
IOpenRowset * pIOpenRowset = NULL;  
hr = IDBInitialize->QueryInterface(IID_IDBCreateSession, (void**)&pIDBCreateSession));  
hr = pIDBCreateSession->CreateSession(  
   NULL,             // pUnkOuter  
   IID_IOpenRowset,  // riid  
  &pIOpenRowset ));  // ppSession  
  
// Create a rowset with a firehose mode cursor.  
IRowset *pIRowset = NULL;  
DBPROP rgRowsetProperties[2];  
  
// To get a firehose mode cursor request a   
// forward only read only rowset.  
rgRowsetProperties[0].dwPropertyID = DBPROP_IRowsetLocate;  
rgRowsetProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;  
rgRowsetProperties[0].dwStatus = DBPROPSTATUS_OK;  
rgRowsetProperties[0].colid = DB_NULLID;  
VariantInit(&(rgRowsetProperties[0].vValue));  
rgRowsetProperties[0].vValue.vt = VARIANT_BOOL;  
rgRowsetProperties[0].vValue.boolVal = VARIANT_FALSE;  
  
rgRowsetProperties[1].dwPropertyID = DBPROP_IRowsetChange;  
rgRowsetProperties[1].dwOptions = DBPROPOPTIONS_REQUIRED;  
rgRowsetProperties[1].dwStatus = DBPROPSTATUS_OK;  
rgRowsetProperties[1].colid = DB_NULLID;  
VariantInit(&(rgRowsetProperties[1].vValue));  
rgRowsetProperties[1].vValue.vt = VARIANT_BOOL;  
rgRowsetProperties[1].vValue.boolVal = VARIANT_FALSE;  
  
DBPROPSET rgRowsetPropSet[1];  
rgRowsetPropSet[0].rgProperties = rgRowsetProperties  
rgRowsetPropSet[0].cProperties = 2  
rgRowsetPropSet[0].guidPropertySet = DBPROPSET_ROWSET;  
  
hr = pIOpenRowset->OpenRowset (NULL,  
   &TableID,  
   NULL,  
   IID_IRowset,  
   1,  
   rgRowsetPropSet  
   (IUnknown**)&pIRowset);  

Драйвер ODBC для собственного клиента SQL Server

Драйвер ODBC собственного клиента SQL Server поддерживает MARS через дополнения к функциям SQLSetConnectAttr и SQLGetConnectAttr. Добавление SQL_COPT_SS_MARS_ENABLED выполнено, чтобы принять значение SQL_MARS_ENABLED_YES или SQL_MARS_ENABLED_NO; по умолчанию принимается значение SQL_MARS_ENABLED_NO. Кроме того, добавлено новое ключевое слово строки подключения Mars_Connection. Оно принимает значения «да» или «нет»; по умолчанию принимается значение «нет».

Пример драйвера ODBC для собственного клиента SQL Server

В этом примере функция SQLSetConnectAttr используется для включения MARS перед вызовом функции SQLDriverConnect для подключения к базе данных. После создания подключения вызываются две функции SQLExecDirect для создания двух отдельных результирующих наборов в одном соединении.

#include <sqlncli.h>  
  
SQLSetConnectAttr(hdbc, SQL_COPT_SS_MARS_ENABLED, SQL_MARS_ENABLED_YES, SQL_IS_UINTEGER);  
SQLDriverConnect(hdbc, hwnd,   
   "DRIVER=SQL Server Native Client 10.0;  
   SERVER=(local);trusted_connection=yes;", SQL_NTS, szOutConn,   
   MAX_CONN_OUT, &cbOutConn, SQL_DRIVER_COMPLETE);  
  
SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt1);  
SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt2);  
  
// The 2nd execute would have failed with connection busy error if  
// MARS were not enabled.  
SQLExecDirect(hstmt1, L"SELECT * FROM Authors", SQL_NTS);  
SQLExecDirect(hstmt2, L"SELECT * FROM Titles", SQL_NTS);  
  
// Result set processing can interleave.  
SQLFetch(hstmt1);  
SQLFetch(hstmt2);  

См. также

Компоненты собственного клиента SQL Server
Использование результирующих наборов по умолчанию в SQL Server