ответы на 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) | |
-------------------------------------------------------------------------------------------------------------
Предложения
- Сохраняйте небольшой размер транзакции
- Убедитесь, что последовательность обновления одинакова в разных потоках.
- Добавьте еще один столбец типа unsigned int с автоматическим увеличением
pid
в качестве основной ключ. Это улучшило бы производительность вставки.
Подробнее об этом do c