Будет ли entifty-framework-core автоматически соответствовать row_lock или table_lock до mysql? - PullRequest
2 голосов
/ 06 августа 2020

Я использую mysql с ядром entityframework.

1. Таблица имеет следующий вид :

create table `device_power_duration` (
`Id` varchar(25) NOT NULL PRIMARY KEY,
`DeviceId` varchar(25) NOT NULL,
`StartTime` datetime(6) NOT NULL,
`PowerSupplyDuration` datetime(6) NOT NULL
) ENGINE=InnoDB;

2. Операции

Каждые 30 секунд будет выполняться 1000 запросов на обновление таблицы. Каждый запрос будет выполнять следующую работу:

  • Если deviceId не существует, создайте его.
  • Если deviceId существует, обновите столбец PowerSupplyDuration этого deviceId .

Операции для каждого запроса аналогичны следующему коду:

var device =_context.Devices.AsNoTracking().Where(x=>x.DeviceId=="xxxxx").First();
if(device==null)
{
    //create a device
}
else
{
    //update the device
}

3. Вопросы:

  • Какой замок использовать? row-lock или table-lock?
  • Подходит ли entityframeworkcore auto к блокировке?
  • Будет ли тупиковая блокировка в ситуации 1000 requests?

1 Ответ

1 голос
/ 07 августа 2020

ответы на Q1 и Q3

Q1 : Я предлагаю вам использовать innodb, и innodb будет автоматически управлять блокировками на уровне строк

Q3 : я боюсь, что возникнет взаимоблокировка из-за вашего текущего рабочего процесса и структуры таблицы. См. Более подробную информацию ниже.

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

Тестовые данные и таблицы, бизнес-процесс

create table `device_power_duration` (
`Id` varchar(25) NOT NULL PRIMARY KEY,
`DeviceId` varchar(25) NOT NULL,
`StartTime` datetime(6) NOT NULL,
`PowerSupplyDuration` datetime(6) NOT NULL,
index idx_dev_id (`DeviceId`)
) ENGINE=InnoDB;

insert into `device_power_duration` (Id, DeviceId, StartTime, PowerSupplyDuration)
VALUES ('1', '1', now(), now()),
       ('3', '3', now(), now()),
       ('5', '5', now(), now()),
       ('7', '7', now(), now());

Рабочий процесс выглядит следующим образом:

public bool UpdateDeviceRecord(Guid id,bool deviceIsRunning)
{
    var list = _context.Ddj_Device_Records.AsNoTracking()
                    .Where(x => x.DeviceNo == id.ToString() && (DateTime.Now - x.UpdatedTime).TotalMinutes > 30).ToList();
    for (int i = 0; i < list.Count; i++)
    {
        list[i].PowerSupplyFinished = true;
    }
    _context.SaveChanges();
    var record = _context.Ddj_Device_Records.AsNoTracking()
        .Where(x => x.DeviceNo == id.ToString() && (DateTime.Now - x.UpdatedTime).TotalMinutes < 30).FirstOrDefault();
    if (record == null)
    {
        //create a new record
    }
    else
    {
        record.UpdatedTime = DateTime.Now;
        if (!deviceIsRunning)
        {
             record.PowerSupplyFinished = false;
        }
    }
    _context.SaveChanges();
}

Параллельная вставка с тем же Id и DeviceId

Фактически будет вызвано первичным ключом Id.

session1 выполнить откат вместо фиксации после вставки. Тогда произойдет тупик. Этот откат может быть вызван в виде исключения в вашем приложении или в других ненормальных ситуациях, и он связан с вашей mysql структурой.

---------------------------------------------------------------------------------------------------------
session1                        |   session2                        |   session3
---------------------------------------------------------------------------------------------------------
insert (Id='2', DeviceId='2')   |                                   |
---------------------------------------------------------------------------------------------------------
                                |   insert (Id='2', DeviceId='2')   |
                                |   Blocked                         |
---------------------------------------------------------------------------------------------------------
                                |                                   |   insert (Id='2', DeviceId='2')
                                |                                   |   Blocked
---------------------------------------------------------------------------------------------------------
rollback                        |                                   |
---------------------------------------------------------------------------------------------------------
                                |   Success                         |   
---------------------------------------------------------------------------------------------------------
                                |                                   |   deadlock
---------------------------------------------------------------------------------------------------------

Информация о взаимоблокировке следующая:

*** (1) TRANSACTION:
TRANSACTION 4274, ACTIVE 12 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 104, OS thread handle 139681802041088, query id 24311 172.22.0.1 root update
/* ApplicationName=PyCharm 2019.1.1 */ insert into `device_power_duration` (Id, DeviceId, StartTime, PowerSupplyDuration)
VALUES ('2', '2', now(), now())
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 116 page no 3 n bits 72 index PRIMARY of table `test`.`device_power_duration` trx id 4274 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 1; hex 33; asc 3;;
 1: len 6; hex 00000000109f; asc       ;;
 2: len 7; hex f3000001e7011a; asc        ;;
 3: len 1; hex 33; asc 3;;
 4: len 8; hex 99a70e37b7000000; asc    7    ;;
 5: len 8; hex 99a70e37b7000000; asc    7    ;;

*** (2) TRANSACTION:
TRANSACTION 4275, ACTIVE 9 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 2 row lock(s)
MySQL thread id 105, OS thread handle 139681803122432, query id 24321 172.22.0.1 root update
/* ApplicationName=PyCharm 2019.1.1 */ insert into `device_power_duration` (Id, DeviceId, StartTime, PowerSupplyDuration)
VALUES ('2', '2', now(), now())
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 116 page no 3 n bits 72 index PRIMARY of table `test`.`device_power_duration` trx id 4275 lock mode S locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 1; hex 33; asc 3;;
 1: len 6; hex 00000000109f; asc       ;;
 2: len 7; hex f3000001e7011a; asc        ;;
 3: len 1; hex 33; asc 3;;
 4: len 8; hex 99a70e37b7000000; asc    7    ;;
 5: len 8; hex 99a70e37b7000000; asc    7    ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 116 page no 3 n bits 72 index PRIMARY of table `test`.`device_power_duration` trx id 4275 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 1; hex 33; asc 3;;
 1: len 6; hex 00000000109f; asc       ;;
 2: len 7; hex f3000001e7011a; asc        ;;
 3: len 1; hex 33; asc 3;;
 4: len 8; hex 99a70e37b7000000; asc    7    ;;
 5: len 8; hex 99a70e37b7000000; asc    7    ;;

*** WE ROLL BACK TRANSACTION (2)

Параллельно обновлять записи в другом порядке

Ситуация 1

Исходя из текущего рабочего процесса, MySQL сортирует записи по своему усмотрению без какой-либо гарантии последовательность. Следовательно, два параллельных потока могут обновлять один и тот же list (var) в разном порядке. Затем происходит взаимоблокировка.

Подробнее о порядке по умолчанию здесь

Ситуация 2

Даже если все записи были отсортированы по идентификатору, возможна взаимоблокировка, как в следующих ситуациях: Ключевым моментом является то, что record сеанса 1 может стать элементом списка сеанса 2, поскольку время всегда меняется.

-------------------------------------------------------------------------------------------------------------
session1                                            |   session2                                            | 
-------------------------------------------------------------------------------------------------------------
list=(id[5,7])                                      |                                                       |
-------------------------------------------------------------------------------------------------------------
update PowerSupplyFinished when(id in [5,7])        |                                                       |
-------------------------------------------------------------------------------------------------------------
record=4                                            |                                                       | 
-------------------------------------------------------------------------------------------------------------
                                                    |   list=(id[4,5,7])                                    |
-------------------------------------------------------------------------------------------------------------
                                                    |   update PowerSupplyFinished when(id in [4,5,7])      |  
                                                    |   block(hold id=4 lock, required id=5&7 locks)        | 
-------------------------------------------------------------------------------------------------------------
update PowerSupplyFinished when(id=4)               |                                                       |
deadlock(required id=4 lock, hold id=5&7 locks)     |                                                       |
-------------------------------------------------------------------------------------------------------------

Предложения

  1. Сохраняйте небольшой размер транзакции
  2. Убедитесь, что последовательность обновления одинакова в разных потоках.
  3. Добавьте еще один столбец типа unsigned int с автоматическим увеличением pid в качестве основной ключ. Это улучшило бы производительность вставки.

Подробнее об этом do c

...