Как я могу написать с параллельными потоками в одну таблицу

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

Я использую Microsoft SQL Server 2014 с одной оптимизированной для памяти таблицей и встроенной скомпилированной хранимой процедурой для записи.

Мое приложение считывает тонны данных (виртуальные сделки) в эту таблицу, оптимизированную для памяти:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Trades]
(
    [asset_id] [bigint] NOT NULL,
    [trade_id] [bigint] NOT NULL,
    [BNP] [money] NOT NULL,
    [LSP] [money] NULL,
    [expires] [int] NULL,
    [timestamp] [bigint] NULL,
    [currentBid] [money] NULL,
    [startingBid] [money] NULL,
    [pos] [nchar](3) COLLATE Latin1_General_BIN2 NOT NULL,
    [rating] [int] NOT NULL,

INDEX [asset] NONCLUSTERED HASH 
(
    [asset_id],
    [BNP]
)WITH ( BUCKET_COUNT = 32),
    CONSTRAINT [Trades_primaryKey] PRIMARY KEY NONCLUSTERED HASH 
(
[trade_id]
)WITH ( BUCKET_COUNT = 64)
)WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA )
GO

Хранимая процедура вычисляет при каждом вызове среднюю цену для столбца. BNP для всех строк с одинаковыми asset_id чтобы выяснить, является ли текущая сделка очень дешевой:

CREATE PROCEDURE [dbo].[ADD_TRADE_]
    @asset int,             -- asset_id
    @trade nchar(12),       -- trade_id
    @BNP int,               -- BuyNowPrice
    @LSP int,               -- LastSellPrice
    @expires nchar(16),     -- expiration in seconds
    @timestamp bigint,  -- current timestamp 
    @currentBid int,        -- current Bid on trade
    @startingBid int,       -- starting Bid on trade
    @pos nchar(3),          -- Player Position
    @rating int,            -- Card Rating
    @MIN_TRADE_CNT int,
    @BUY_PERCENTAGE int,
    @AVG_BNP int
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS BEGIN ATOMIC WITH
(
 TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english'
)
    --CONFIG

    /* RATING TO COLOR
    DECLARE @BRONZE int = 64
    DECLARE @SILVER int = 74
    */
    /* 1 //// INSERT FRESH TRADES ////////////////////// */
    --TMP VARS
    DECLARE @trade_cnt int = 0
    DECLARE @exists bit = 0

    --CHECK IF TRADE EXISTS
    SELECT  @exists=1
        FROM [dbo].[Trades] 
        WHERE trade_id = @trade
    -- ADD IF NOT
    IF @exists=0
    BEGIN 
        INSERT INTO [dbo].[Trades]
        (asset_id,trade_id,BNP,LSP,expires,timestamp,currentBid,startingBid,pos,rating)
    VALUES(@asset, @trade,@BNP,@LSP,@expires,@timestamp,@currentBid,@startingBid,@pos,@rating)

        //// CHECK IF TRADE IS A GOOD ONE TO BUY ////////////////////
        DECLARE @MIN_BID_PRICE money  = 0
        SELECT @trade_cnt=COUNT(*) FROM dbo.Trades;

        SET @MIN_BID_PRICE = (@AVG_BNP/100) * @BUY_PERCENTAGE

        /// IF YES , ADD TO BID TABLE
        IF @trade_cnt >= @MIN_TRADE_CNT AND @BNP > 0 AND @BNP < @MIN_BID_PRICE 
        BEGIN
            INSERT INTO [dbo].[Bids](trade_id,bid,max) VALUES(@trade,@BNP,@MIN_BID_PRICE)
        END
    END
END

Так что он не только пишет параллельно, он также читает параллельно. Теперь моя проблема в том, что некоторые записи блокируются, и я получаю эту ошибку:A previous transaction that the current transaction took a dependency on has aborted, and the current transaction can no longer commit.

Если я обработаю эту ошибку и использую логику повторных попыток, некоторые из этих повторных попыток все еще блокируются до 10 раз, и это не только блокирует один конкретный поток работника, но и создает большой стек потоков, ожидающих своей очереди на запись,

Я искал и нашел страницу MSDN, где их описывают уровни изоляции И я думаю, read committed это то, что мне нужно Но кажется, что я не могу использовать это в таблице, оптимизированной для памяти.

Что я могу изменить в своем коде или я могу использовать альтернативную систему БД для этого?

1 ответ

Как вы получаете ваши данные в вашей хранимой процедуре? Если это через.NET-приложение, вам, вероятно, лучше создать DataTable в памяти для всех ваших столбцов - включая вычисленный столбец, а затем с помощью SqlBulkCopy класс, чтобы написать все эти данные за один раз. если у вас есть слишком много, вы всегда можете разбить его на партии.

Это намного эффективнее, чем пытаться писать по одной строке за раз!

Другие вопросы по тегам