Страницы

Поиск по вопросам

четверг, 27 февраля 2020 г.

Как работает SqlBulkCopy в .NET “за кулисами”?

#net #sql_server


Интереса ради заглянул в исходники, чтобы понять как представление данных в памяти
пуляется на сервер, однако, что-то не нашел этого места.

Билдер команды нашел, но, вроде, в нем нет ничего сверхъестественного.

Нашел, где данные хранятся перед отправкой - независимо от того, что подается на
вход, они копируются в массив, но самого интересного не нашел...
    


Ответы

Ответ 1



Общение клиентских приложений с SqlServer происходит посредством протокола TDS (Tabular Data Stream Protocol), спецификацию которого можно найти здесь. При bulk-загрузке клиент посылает серверу сообщение типа Bulk Load (раздел 2.2.1.5 спецификации): 2.2.1.5 Bulk Load ... The client sends the INSERT BULK SQL statement and then sends a COLMETADATA token that describes the raw data. Multiple rows of binary data are then sent to the server. ... т.е. серверу передаются (последовательно) INSERT BULK команда (специальная разновидность INSERT, не путать с BULK INSERT) метаданные столбцов передаваемых данных собственно сами строки данных Если сообщение длинное (например, текст команды длинный, или данных много, что типично для bulk-загрузки), то оно передаётся несколькими пакетами. Для осуществления взаимодействия посредством TDS-протокола SqlBulkCopy использует вспомогательный класс TdsParser. Вообще TdsParser (вместе с TdsParserStateObject) является одним из краеугольных классов во внутренней инфраструктуре System.Data.SqlClient (и используется также такими классами как, например, SqlCommand, SqlDataReader, SqlConnection). Последовательность от вызова метода WriteToServer (WriteToServerAsync) до отправки данных на сервер примерно такая: WriteToServer WriteRowSourceToServerAsync WriteToServerInternalAsync ReadFromRowSourceAsync WriteToServerInternalRestAsync CreateAndExecuteInitialQueryAsync CreateInitialQuery WriteToServerInternalRestContinuedAsync AnalyzeTargetAndCreateUpdateBulkCommand CopyBatchesAsync SubmitUpdateBulkCommand CopyBatchesAsyncContinued WriteMetaData CopyRowsAsync CopyColumnsAsync ReadWriteColumnValueAsync GetValueFromSourceRow TdsParser.WriteBulkCopyValue Из этого наиболее интересно следующее. Метод CreateInitialQuery формирует команду SET FMTONLY ON select * from [TableName] SET FMTONLY OFF которая используется для получения информации о столбцах целевой таблицы. Метод AnalyzeTargetAndCreateUpdateBulkCommand формирует INSERT BULK команду наподобие, например, такой insert bulk [TableName] ([Id] int) with (TABLOCK) Метод SubmitUpdateBulkCommand передаёт сформированный текст команды на сервер. Метод WriteMetaData передаёт метаданные столбцов. В этом методе, что интересно, перед передачей метаданных есть строка кода _stateObj._outputMessageType = TdsEnums.MT_BULK; (префикс MT_ означает message type). Это значит, что передача Bulk load сообщения инициируется здесь, а не с отправки команды INSERT BULK несколько ранее в методе SubmitUpdateBulkCommand. Если вернуться ненадолго в этот метод, то можно найти там вызов TdsExecuteSQLBatch, проследовав внутрь которого, можно обнаружить строку кода stateObj._outputMessageType = TdsEnums.MT_SQL; т.е. текст команды посылается отдельным сообщением. Это несколько неожиданно, т.к. читая раздел 2.2.1.5 спецификации сложилось впечатление, что и текст команды также должен быть частью Bulk load сообщения. Впрочем, в разделе 2.2.6.1 спецификации 2.2.6.1 Bulk Load BCP ... This message sent to the server contains bulk data to be inserted. The client MUST have previously notified the server where this data is to be inserted. For more information about the INSERT BULK syntax, see [MSDN-INSERT]. ... поясняется (недостаточно явно, на мой взгляд), что Bulk load сообщение содержит данные для вставки, а перед его отправкой серверу нужно сообщить куда вставлять данные. Таким образом, bulk-загрузка происходит посредством отправки двух сообщений серверу: INSERT BULK команда (куда вставляем) метаданные столбцов данных + строки данных (что вставляем) Следуем дальше. Метод CopyRowsAsync отвечает за передачу строк данных. Внутри ReadWriteColumnValueAsync происходит вызов метода WriteBulkCopyValue класса TdsParser. Внутри метода WriteBulkCopyValue последовательность вызовов примерно следующая TdsParser.WriteBulkCopyValue TdsParser.WriteValue/WriteSqlValue/WriteShort/WriteInt/WriteLong/WriteDouble/... TdsParserStateObject.WriteByte/WriteByteArray TdsParserStateObject.WritePacket TdsParserStateObject.WriteSni TdsParserStateObject.SNIWritePacket SNINativeMethodWrapper.SNIWritePacket Логика разнообразных вызовов, происходящих внутри WriteBulkCopyValue, заключается в основном в том, что данные (в байтовом представлении) пишутся в буфер, пока он не полон. Когда же буфер (будущий TDS-пакет) заполнился (TDS-пакет готов), происходит его отправка на сервер (метод SNIWritePacket). Внутри SNIWritePacket вызов метода SNINativeMethodWrapper.SNIWritePacket - это обращение ко внешней библиотеке sni.dll, которая обеспечивает взаимодействие с транспортным уровнем (Shared Memory, Named Pipes, TCP).

Ответ 2



SqlBulkCopy использует datatable, IDataReader или DataRow[] в качестве источника данных. Обратите внимание на SqlBulkCopy.WriteToServer если хотите больше подробностей. А также на The Data Loading Performance Guide, там рассматриватся BCP, но информация о BulkCopy также представлена.

Комментариев нет:

Отправить комментарий