select...for update锁详解

select…for update锁详解

select…for update的作用就是:如果A事务中执行了select…for update,那么在其提交或回滚事务之前,B,C,D…事务是无法操作(写)A事务select…for update所命中的数据的!

前提条件

MySQL中通过select…for update实现悲观锁

select…for update仅适用于InnoDB,且查询需命中,事务需在手动提交下才能生效
MySQL事务隔离级别的不同会影响到上锁策略,本文内容基于MySQL默认隔离级别可重复读(REPEATABLE-READ)

-- 查询当前事务隔离级别
-- MySQL5.7.20版本之前
SHOW VARIABLES LIKE 'tx_isolation';
-- MySQL5.7.20版本及之后
SHOW VARIABLES LIKE 'transaction_isolation';
-- 都可使用
SELECT @@transaction_isolation;

-- 设置事务隔离级别为REPEATABLE-READ;session范围为当前会话,global全局范围
-- 方式1:
-- SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔离级别;
-- 其中,隔离级别格式:
-- > READ UNCOMMITTED
-- > READ COMMITTED
-- > REPEATABLE READ
-- > SERIALIZABLE
set session transaction isolation level repeatable read;
-- 或
set global transaction isolation level repeatable read;

-- 方式2:
-- SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别'
-- 其中,隔离级别格式:
-- > READ-UNCOMMITTED
-- > READ-COMMITTED
-- > REPEATABLE-READ
-- > SERIALIZABLE
SET SESSION TRANSACTION_ISOLATION = 'REPEATABLE-READ';
-- 或
SET GLOBAL TRANSACTION_ISOLATION = 'REPEATABLE-READ';

行锁,表锁

行锁:锁的是数据行,一条或多条数据
表锁:锁的是表,将整张表的全部数据锁住

select…for update锁的是行还是表,可以通过相关查询判断:

-- 查看正在执行的事务列表
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
-- 查看正在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
-- 查看等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-- 查询是否锁表
SHOW OPEN TABLES WHERE In_use > 0;
-- 查询进程
SHOW PROCESSLIST;
-- 查询死锁信息
SHOW ENGINE INNODB STATUS;
-- 杀死进程
KILL <thread_id>;
-- 设置超时时间防止死锁
SET innodb_lock_wait_timeout = <timeout_value>;

相关表字段分析:

innodb_trx表:
trx_id:事务ID。
trx_state:事务状态,有以下几种状态:RUNNING、LOCK WAIT、ROLLING BACK 和 COMMITTING。
trx_started:事务开始时间。
trx_requested_lock_id:事务当前正在等待锁的标识,可以和 INNODB_LOCKS 表 JOIN 以得到更多详细信息。
trx_wait_started:事务开始等待的时间。
trx_weight:事务的权重。
trx_mysql_thread_id:事务线程 ID,可以和 PROCESSLIST 表 JOIN。
trx_query:事务正在执行的 SQL 语句。
trx_operation_state:事务当前操作状态。
trx_tables_in_use:当前事务执行的 SQL 中使用的表的个数。
trx_tables_locked:当前执行 SQL 的行锁数量。
trx_lock_structs:事务保留的锁数量。
trx_lock_memory_bytes:事务锁住的内存大小,单位为 BYTES。
trx_rows_locked:事务锁住的记录数。包含标记为 DELETED,并且已经保存到磁盘但对事务不可见的行。
trx_rows_modified:事务更改的行数。
trx_concurrency_tickets:事务并发票数。
trx_isolation_level:当前事务的隔离级别。
trx_unique_checks:是否打开唯一性检查的标识。
trx_foreign_key_checks:是否打开外键检查的标识。
trx_last_foreign_key_error:最后一次的外键错误信息。
trx_adaptive_hash_latched:自适应散列索引是否被当前事务锁住的标识。
trx_adaptive_hash_timeout:是否立刻放弃为自适应散列索引搜索 LATCH 的标识。
 
innodb_locks表:
lock_id:锁 ID。
lock_trx_id:拥有锁的事务 ID。可以和 INNODB_TRX 表 JOIN 得到事务的详细信息。
lock_mode:锁的模式。有如下锁类型:行级锁包括:S、X、IS、IX,分别代表:共享锁、排它锁、意向共享锁、意向排它锁。表级锁包括:S_GAP、X_GAP、IS_GAP、IX_GAP 和 AUTO_INC,分别代表共享间隙锁、排它间隙锁、意向共享间隙锁、意向排它间隙锁和自动递增锁。
lock_type:锁的类型。RECORD 代表行级锁,TABLE 代表表级锁。
lock_table:被锁定的或者包含锁定记录的表的名称。
lock_index:当 LOCK_TYPE=’RECORD’ 时,表示索引的名称;否则为 NULL。
lock_space:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的表空间 ID;否则为 NULL。
lock_page:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的页号;否则为 NULL。
lock_rec:当 LOCK_TYPE=’RECORD’ 时,表示一堆页面中锁定行的数量,亦即被锁定的记录号;否则为 NULL。
lock_data:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的主键;否则为NULL。
 
innodb_lock_waits表:
requesting_trx_id:请求事务的 ID。
requested_lock_id:事务所等待的锁定的 ID。可以和 INNODB_LOCKS 表 JOIN。
blocking_trx_id:阻塞事务的 ID。
blocking_lock_id:某一事务的锁的 ID,该事务阻塞了另一事务的运行。可以和 INNODB_LOCKS 表 JOIN

查询条件影响锁规则

where条件:

条件说明行锁/表锁
主键主键字段作为查询条件行锁
索引索引字段作为查询条件行锁
普通字段普通字段作为查询条件表锁
-- id字段为主键  
-- age字段创建B-TREE索引
CREATE TABLE stu (
 id INT ( 11 ) NOT NULL AUTO_INCREMENT,
 name VARCHAR ( 255 ) DEFAULT NULL,
 age INT ( 11 ) DEFAULT NULL,
 code VARCHAR ( 255 ) DEFAULT NULL,
 PRIMARY KEY ( id ),
 KEY idx_age ( age ) USING BTREE 
) ENGINE = INNODB AUTO_INCREMENT = 1570068 DEFAULT CHARSET = utf8;

image.png

测试1

图1
image.png

图2
image.png

图3
image.png

分析:

  • 图1:A事务执行select * from stu where id = 1001 for update; 以主键字段作为查询条件
  • 图2:B事务执行update stu set name = ‘zs’ where id = 1001; 对id为1001的数据(事务A查询出的数据)进行修改,阻塞等待,最后长时间拿不到锁报错
  • 图3:B事务执行update stu set name = ‘ls’ where id = 1009; 对其他数据进行修改,修改成功

总结:
以主键字段作为条件上锁,锁的是行,是表中查询命中的数据

测试2

图1
image.png

图2
image.png

图3
image.png

分析:

  • 图1:A事务执行select * from stu where age = 22 for update; 以索引字段作为查询条件
  • 图2:B事务执行update stu set name = ‘ls’ where id = 1009; 对id为1009的数据(事务A查询出的数据)进行修改,阻塞等待,最后长时间拿不到锁报错
  • 图3:B事务执行update stu set name = ‘张三’ where id = 1001; 对其他数据进行修改,修改成功

总结:
以索引字段作为条件上锁,锁的是行,是表中查询命中的数据

测试3

图1
image.png

图2
image.png

图3
image.png

分析:

  • 图1:A事务执行select * from stu where code = ‘JS1001’ for update; 以普通字段作为查询条件
  • 图2:B事务执行update stu set name = ‘章山’ where id = 1001; 对id为1001的数据(事务A查询出的数据)进行修改,阻塞等待,最后长时间拿不到锁报错
  • 图3:B事务执行update stu set name = ‘Lisa’ where id = 1009; 对其他数据进行修改,阻塞等待,最后长时间拿不到锁报错

总结:
以普通字段作为条件上锁,锁的是表,该表的全部数据

组合条件

and组合条件
条件行锁/表锁
主键 and 索引行锁
主键 and 普通字段行锁
索引 and 普通字段行锁

or组合条件
条件行锁/表锁
主键 or 索引行锁
主键 or 普通字段表锁
索引 or 普通字段表锁
  • 24
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
yolov7train.py 是使用 YOLOv7 算法进行目标检测的训练脚本。下面对 yolov7train.py 的主要代码进行简单的解释: 1. 导入相关库 ```python import argparse import yaml import time import torch from torch.utils.data import DataLoader from torchvision import datasets from models.yolov7 import Model from utils.datasets import ImageFolder from utils.general import ( check_img_size, non_max_suppression, apply_classifier, scale_coords, xyxy2xywh, plot_one_box, strip_optimizer, set_logging) from utils.torch_utils import ( select_device, time_synchronized, load_classifier, model_info) ``` 这里导入了 argparse 用于解析命令行参数,yaml 用于解析配置文件,time 用于记录时间,torch 用于神经网络训练,DataLoader 用于读取数据集,datasets 和 ImageFolder 用于加载数据集,Model 用于定义 YOLOv7 模型,各种工具函数用于辅助训练。 2. 定义命令行参数 ```python parser = argparse.ArgumentParser() parser.add_argument('--data', type=str, default='data.yaml', help='dataset.yaml path') parser.add_argument('--hyp', type=str, default='hyp.yaml', help='hyperparameters path') parser.add_argument('--epochs', type=int, default=300) parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs') parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='[train, test] image sizes') parser.add_argument('--rect', action='store_true', help='rectangular training') parser.add_argument('--resume', nargs='?', const='yolov7.pt', default=False, help='resume most recent training') parser.add_argument('--nosave', action='store_true', help='only save final checkpoint') parser.add_argument('--notest', action='store_true', help='only test final epoch') parser.add_argument('--evolve', action='store_true', help='evolve hyperparameters') parser.add_argument('--bucket', type=str, default='', help='gsutil bucket') opt = parser.parse_args() ``` 这里定义了许多命令行参数,包括数据集路径、超参数路径、训练轮数、批量大小、图片大小、是否使用矩形训练、是否从最近的检查点恢复训练、是否只保存最终的检查点、是否只测试最终的模型、是否进行超参数进化、gsutil 存储桶等。 3. 加载数据集 ```python with open(opt.data) as f: data_dict = yaml.load(f, Loader=yaml.FullLoader) train_path = data_dict['train'] test_path = data_dict['test'] num_classes = data_dict['nc'] names = data_dict['names'] train_dataset = ImageFolder(train_path, img_size=opt.img_size[0], rect=opt.rect) test_dataset = ImageFolder(test_path, img_size=opt.img_size[1], rect=True) batch_size = opt.batch_size train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=8, pin_memory=True, collate_fn=train_dataset.collate_fn) test_dataloader = DataLoader(test_dataset, batch_size=batch_size * 2, num_workers=8, pin_memory=True, collate_fn=test_dataset.collate_fn) ``` 这里读取了数据集的配置文件,包括训练集、测试集、类别数和类别名称等信息。然后使用 ImageFolder 加载数据集,设置图片大小和是否使用矩形训练。最后使用 DataLoader 加载数据集,并设置批量大小、是否 shuffle、是否使用 pin_memory 等参数。 4. 定义 YOLOv7 模型 ```python model = Model(opt.hyp, num_classes, opt.img_size) model.nc = num_classes device = select_device(opt.device, batch_size=batch_size) model.to(device).train() criterion = model.loss optimizer = torch.optim.SGD(model.parameters(), lr=hyp['lr0'], momentum=hyp['momentum'], weight_decay=hyp['weight_decay']) scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=1, T_mult=2) start_epoch = 0 best_fitness = 0.0 ``` 这里使用 Model 类定义了 YOLOv7 模型,并将其放到指定设备上进行训练。使用交叉熵损失函数作为模型的损失函数,使用 SGD 优化器进行训练,并使用余弦退火学习率调整策略。定义了起始轮数、最佳精度等变量。 5. 开始训练 ```python for epoch in range(start_epoch, opt.epochs): model.train() mloss = torch.zeros(4).to(device) # mean losses for i, (imgs, targets, paths, _) in enumerate(train_dataloader): ni = i + len(train_dataloader) * epoch # number integrated batches (since train start) imgs = imgs.to(device) targets = targets.to(device) loss, _, _ = model(imgs, targets) loss.backward() optimizer.step() optimizer.zero_grad() mloss = (mloss * i + loss.detach().cpu()) / (i + 1) # update mean losses # Print batch results if ni % 20 == 0: print(f'Epoch {epoch}/{opt.epochs - 1}, Batch {i}/{len(train_dataloader) - 1}, lr={optimizer.param_groups[0]["lr"]:.6f}, loss={mloss[0]:.4f}') # Update scheduler scheduler.step() # Update Best fitness with torch.no_grad(): fitness = model_fitness(model) if fitness > best_fitness: best_fitness = fitness # Save checkpoint if (not opt.nosave) or (epoch == opt.epochs - 1): ckpt = { 'epoch': epoch, 'best_fitness': best_fitness, 'state_dict': model.state_dict(), 'optimizer': optimizer.state_dict() } torch.save(ckpt, f'checkpoints/yolov7_epoch{epoch}.pt') # Test if not opt.notest: t = time_synchronized() model.eval() for j, (imgs, targets, paths, shapes) in enumerate(test_dataloader): if j == 0: pred = model(imgs.to(device)) pred = non_max_suppression(pred, conf_thres=0.001, iou_thres=0.6) else: break t1 = time_synchronized() if isinstance(pred, int) or isinstance(pred, tuple): print(f'Epoch {epoch}/{opt.epochs - 1}, test_loss={mloss[0]:.4f}, test_mAP={0.0}') else: pred = pred[0].cpu() iou_thres = 0.5 niou = [iou_thres] * num_classes ap, p, r = ap_per_class(pred, targets, shapes, iou_thres=niou) mp, mr, map50, f1, _, _ = stats(ap, p, r, gt=targets) print(f'Epoch {epoch}/{opt.epochs - 1}, test_loss={mloss[0]:.4f}, test_mAP={map50:.2f} ({mr*100:.1f}/{mp*100:.1f})') # Plot images if epoch == 0 and j == 0: for i, det in enumerate(pred): # detections per image img = cv2.imread(paths[i]) # BGR img = plot_results(img, det, class_names=names) cv2.imwrite(f'runs/test{i}.jpg', img) if i == 3: break ``` 这里进行了多个 epoch 的训练。在每个 epoch 中,对于每个批量的数据,先将数据移动到指定设备上,然后计算模型的损失函数,并进行反向传播和梯度下降。在每个 epoch 结束时,更新学习率调整策略和最佳精度,保存当前的检查点。如果 opt.notest 为 False,则进行测试,并输出测试结果。最后,如果是第一个 epoch,则绘制部分图像用于可视化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值