shell执行命令时,判断没有相同进程再执行,常用于定时任务
if ! ps aux | grep -v grep | grep -q "msg run"; then msg run; fi
#测试
if ! ps aux | grep -v grep | grep -q "msg run"; then echo msg run; fi if ! ps aux | grep -v grep | grep -q "msg run"; then msg run; fi
#测试
if ! ps aux | grep -v grep | grep -q "msg run"; then echo msg run; fi **
在 MySQL 并发事务场景中,SELECT ... FOR UPDATE 是保证数据一致性的重要工具,但很多开发者对其锁定范围、阻塞逻辑及底层原理理解不深。本文结合实战场景,从基础概念到进阶原理,全面拆解 FOR UPDATE 锁机制,帮你彻底掌握其使用逻辑。
一、FOR UPDATE 核心定位:什么是行级锁?
SELECT ... FOR UPDATE 是 MySQL 中用于行级锁定的查询语句,仅在 InnoDB 存储引擎下生效,核心作用是在事务中锁定查询匹配的资源(行或范围),防止其他事务对这些资源进行修改或加排他锁,直到当前事务提交(COMMIT)或回滚(ROLLBACK)。
-- 2. 事务中锁定单行
BEGIN;
SELECT count FROM counter WHERE id = 1 FOR UPDATE; -- 仅锁1行
UPDATE counter SET count = count + 1 WHERE id = 1;
COMMIT;
场景 2:带 WHERE 条件的查询 —— 行级锁还是表级锁?
WHERE 条件是决定锁定粒度的核心,关键在于查询是否能通过索引定位到行:
情况 A:WHERE 条件命中有效索引(行级锁)
若 WHERE 条件使用主键、唯一索引或普通索引,InnoDB 会精准锁定符合条件的行,不影响其他行的操作。
示例:
-- 表结构(id 为主键索引)
CREATE TABLE test_lock (
id INT PRIMARY KEY,
value INT
) ENGINE=InnoDB;
-- 事务A(锁定 id=1 的行)
BEGIN;
SELECT * FROM test_lock WHERE id = 1 FOR UPDATE;
-- 事务B(操作 id=2 的行,无阻塞)
BEGIN;
SELECT * FROM test_lock WHERE id = 2 FOR UPDATE; -- 正常执行
情况 B:WHERE 条件无索引(表级锁)
若 WHERE 条件未使用索引(如 WHERE value = 100 且 value 无索引),InnoDB 会先执行全表扫描定位数据,此时会将全表锁定,所有对该表的操作都会被阻塞。
原因:无索引时,数据库无法快速定位目标行,只能通过全表扫描判断条件,为避免遗漏数据,直接升级为表级锁。
场景 3:范围条件查询(LIKE/BETWEEN)—— 间隙锁与临键锁
当使用范围条件(如 LIKE 'SCJH20250702%'、BETWEEN 10 AND 20)时,即使命中索引,锁定范围也可能超出实际匹配的行,这是 InnoDB 为防止「幻读」设计的「临键锁(Next-Key Lock)」机制。
核心概念
临键锁:锁定「符合条件的行 + 行前后的间隙」,既保证当前查询范围的数据一致性,又防止其他事务插入新行到该范围(避免幻读)。
间隙锁:锁定两个索引值之间的空白区域(如 SCJH20250701999 到 SCJH20250702001 之间的间隙)。
实战现象解析
假设 orderNo 有前缀索引,事务 A 执行:
BEGIN;
SELECT * FROM orders WHERE orderNo LIKE 'SCJH20250702%' FOR UPDATE;
此时事务 B 的操作会出现两种结果:
若事务 B 查询 orderNo LIKE 'SCJH20250701%':其范围与事务 A 的间隙锁(701999~702001)重叠,会被阻塞;
若事务 B 查询 orderNo LIKE 'SCJH20250705%':范围完全不重叠,无锁冲突,可正常执行。
本质:InnoDB 基于索引的有序性,仅锁定「必要的范围」,既保证隔离性,又最大限度保留并发能力。
三、并发安全:为什么 FOR UPDATE 能解决重复值问题?
在「统计数量 + 1」的并发场景中(如生成唯一订单号),若不使用锁机制,会因「竞态条件」导致重复值,而 FOR UPDATE 通过锁定资源实现原子操作。
四、锁机制原理:InnoDB 为什么能精准控制锁定范围?
InnoDB 的锁机制并非 “智能判断”,而是基于「索引有序性」和「隔离性需求」的设计结果,核心逻辑可总结为三点:
六、总结
FOR UPDATE 是 MySQL 并发事务中的核心工具,其锁定范围并非固定,而是受索引、查询条件、数据范围共同影响。掌握其原理需记住三个关键结论:
有索引 + 精确条件→行级锁,无索引→表级锁,范围条件→临键锁;
临键锁(行 + 间隙)是解决幻读的关键,锁定范围可能超出实际匹配行;
锁冲突仅发生在范围重叠时,不重叠的查询可并行执行。
在实际开发中,需结合业务场景选择合适的锁策略,优先使用自增列和索引优化,避免过度锁定,在数据一致性与并发性能之间找到平衡。
在日常 MySQL 开发中,我们常会遇到并发事务导致的数据一致性问题,比如“统计数量+1 出现重复值”,而 FOR UPDATE 作为解决这类问题的关键工具,其锁定逻辑却常让开发者困惑——为何有时只锁目标行,有时阻塞全表,有时不同范围查询也会互相等待?本文结合实战场景,从问题出发,拆解 FOR UPDATE 的核心原理与锁范围控制逻辑。
当两个事务同时执行“统计表中记录数→数量+1→写入新数据”的操作时,若未加锁控制,极易出现“重复值”:
count = 10count = 1010+1=1110+1=11最终两条相同的“11”被写入,违背数据唯一性需求。
FOR UPDATE 是 MySQL InnoDB 引擎提供的行级锁定语句,需在事务中使用,核心作用是:
很多开发者误以为 FOR UPDATE 要么锁行、要么锁表,实则其锁定范围由索引是否有效和查询条件类型共同决定,这也是实战中锁行为差异的核心原因。
当查询条件使用主键、唯一索引或普通索引,且能精确定位数据时,InnoDB 只会锁定符合条件的行,不影响其他行的操作。
假设有订单表 orders,orderNo 字段建立前缀索引(INDEX idx_orderNo (orderNo(20))),数据包含 SCJH20250701001、SCJH20250702001、SCJH20250705001。
事务A(连接1):锁定 7月2日的订单
BEGIN;
SELECT COUNT(id) FROM orders WHERE orderNo LIKE 'SCJH20250702%' FOR UPDATE;
-- 仅锁定 orderNo 以 SCJH20250702 开头的行事务B(连接2):操作 7月5日的订单
BEGIN;
SELECT COUNT(id) FROM orders WHERE orderNo LIKE 'SCJH20250705%' FOR UPDATE;
-- 无需等待,直接执行(锁范围无冲突)若查询条件未使用索引(或索引失效),InnoDB 无法精准定位数据,只能通过全表扫描判断条件,此时会触发表级锁或大范围间隙锁,导致所有对该表的操作都需等待。
若 orderNo 未建索引,事务A执行:
BEGIN;
SELECT COUNT(id) FROM orders WHERE orderNo LIKE 'SCJH20250702%' FOR UPDATE;
-- 无索引导致全表扫描,触发全表锁此时事务B即使查询 7月5日的订单,也会被阻塞,需等待事务A提交/回滚后才能执行。
当查询条件为范围查询(如 LIKE 'xxx%'、BETWEEN)时,即使有索引,InnoDB 也会触发“间隙锁”,锁定范围超出实际匹配的行,这是为了防止“幻读”(事务A查完范围后,事务B插入新行,导致事务A再次查询时数据增多)。
事务A锁定 7月2日的订单(orderNo LIKE 'SCJH20250702%'),InnoDB 会锁定:
orderNo 以 SCJH20250702 开头的行;SCJH20250701999 到 SCJH20250702001、SCJH20250702999 到 SCJH20250703001)。此时事务B若查询 7月1日的订单(SCJH20250701%),其范围与事务A的间隙锁重叠,会被阻塞;而查询 7月5日的订单(SCJH20250705%),范围完全不重叠,则可正常执行——这也是为何“不同范围查询有时等、有时不等”的核心原因。
要深入理解 FOR UPDATE 的锁逻辑,最好的方式是通过并发事务测试,以下是具体步骤:
创建测试表(InnoDB 引擎):
CREATE TABLE test_lock (
id INT PRIMARY KEY,
orderNo VARCHAR(50)
) ENGINE=InnoDB;
INSERT INTO test_lock VALUES
(1, 'SCJH20250701001'),
(2, 'SCJH20250702001'),
(3, 'SCJH20250705001');
-- 建立索引(关键)
CREATE INDEX idx_orderNo ON test_lock(orderNo(20));事务A(连接1):锁定 7月2日订单,不提交
BEGIN;
SELECT * FROM test_lock WHERE orderNo LIKE 'SCJH20250702%' FOR UPDATE;事务B(连接2):查询 7月5日订单
BEGIN;
SELECT * FROM test_lock WHERE orderNo LIKE 'SCJH20250705%' FOR UPDATE;
-- 结果:正常返回,无等待事务B(连接2):查询 7月1日订单
BEGIN;
SELECT * FROM test_lock WHERE orderNo LIKE 'SCJH20250701%' FOR UPDATE;
-- 结果:阻塞等待,直到事务A提交/回滚若需确认锁范围,可通过 MySQL 系统表查询:
-- 查看当前事务与锁等待
SELECT trx_id, trx_state FROM information_schema.INNODB_TRX;
-- 查看锁定的具体范围
SELECT lock_type, lock_mode, lock_data FROM information_schema.INNODB_LOCKS;COMMIT)或回滚(ROLLBACK)后,锁自动释放。AUTO_INCREMENT 比 FOR UPDATE 更高效(数据库自动保证唯一性,无需手动加锁);EXPLAIN 检查索引是否生效;orderNo = 'SCJH20250702001')替代大范围查询,减少间隙锁影响;通过理解 FOR UPDATE 的锁逻辑,我们能在“数据一致性”与“并发性能”之间找到平衡,避免实战中的锁阻塞、数据重复等问题,让 MySQL 事务更高效、更安全。
原脚本
#! /bin/bash
ffmpeg -f concat -safe 0 -i f.txt -c copy out.mp4当使用ffmpeg的concat方式合并视频出现声音异常或画面跳动时,通常是由于源视频的编码参数不一致导致的(如帧率、分辨率、音频采样率等不同)。直接使用-c copy进行流复制会保留原始编码,当参数不匹配时就会出现播放异常。
解决方法是先统一所有视频的编码参数,再进行合并,具体步骤如下:
# 转换单个视频示例
ffmpeg -i input1.mp4 -c:v libx264 -c:a aac -strict experimental -y temp1.mp4
ffmpeg -i input2.mp4 -c:v libx264 -c:a aac -strict experimental -y temp2.mp4
# 对所有视频执行相同操作temp.txt),包含转换后的临时文件:echo "file 'temp1.mp4'" > temp.txt
echo "file 'temp2.mp4'" >> temp.txt
# 添加所有转换后的文件ffmpeg -f concat -safe 0 -i temp.txt -c copy output.mp4这种方法通过先统一编码参数,再进行无损合并,可以解决大部分由于源文件参数不一致导致的音视频同步问题。如果问题仍然存在,可能需要在合并时不使用-c copy,让ffmpeg重新编码一次:
ffmpeg -f concat -safe 0 -i temp.txt -c:v libx264 -c:a aac -strict experimental output.mp4转换过程可能需要一些时间,具体取决于视频数量和大小,但能保证合并后的视频播放正常。
实际运用时,可能还会出现各个视频都使用了不同的编码、音轨导致异常,这里经过了大量的修复版本后,得到最终的版本:
完整脚本:
#!/bin/bash
# 视频合并脚本(调整质量与文件大小)
# 功能:平衡质量与文件大小,避免过度压缩
# ==============================================
# 1. 初始化与参数配置(重点调整CRF值控制压缩率)
# ==============================================
rm -rf temp_converted
rm -f temp_list.txt merged_output.mp4
mkdir -p temp_converted
# 关键参数调整(影响文件大小的核心设置)
CRF_VALUE="18" # 降低CRF值(范围0-51,值越小质量越高/文件越大)
# 建议值:18-23(18接近无损,23平衡,28更高压缩)
FRAME_RATE="30"
KEYFRAME_INTERVAL="60"
VIDEO_RES="1080x1920"
AUDIO_BITRATE="192k" # 适当提高音频码率(从128k→192k)
AUDIO_SAMPLERATE="48000"
AUDIO_CHANNELS="1"
OUTPUT_FILE="merged_output.mp4"
# ==============================================
# 2. 检查MP4文件
# ==============================================
mp4_files=$(ls *.mp4 2>/dev/null | sort -V)
if [ -z "$mp4_files" ]; then
echo -e "\033[31m[!] 未找到MP4文件!\033[0m"
exit 1
fi
file_count=$(echo "$mp4_files" | wc -l)
echo -e "\033[32m[+] 找到 $file_count 个文件\033[0m"
# ==============================================
# 3. 批量转换(使用调整后的质量参数)
# ==============================================
count=1
for file in $mp4_files; do
echo -e "\033[34m[*] [进度 $count/$file_count] 处理:$file\033[0m"
ffmpeg -hide_banner -loglevel error \
-i "$file" \
-map 0:v:0 -map 0:a:0 \
-sn -dn \
-c:v libx264 -crf $CRF_VALUE -preset medium \
-r "$FRAME_RATE" \
-g "$KEYFRAME_INTERVAL" \
-sc_threshold 0 \
-s "$VIDEO_RES" \
-c:a aac -b:a "$AUDIO_BITRATE" \
-ar "$AUDIO_SAMPLERATE" \
-ac "$AUDIO_CHANNELS" \
-async 1000 \
-vsync cfr \
-y "temp_converted/temp_$count.mp4"
if [ $? -ne 0 ]; then
echo -e "\033[31m[!] 处理失败:$file\033[0m"
exit 1
fi
((count++))
done
# ==============================================
# 4. 生成合并列表与合并
# ==============================================
echo -e "\n\033[34m[*] 生成合并列表...\033[0m"
for ((i=1; i<count; i++)); do
echo "file 'temp_converted/temp_$i.mp4'" >> temp_list.txt
done
echo -e "\n\033[34m[*] 合并视频...\033[0m"
ffmpeg -hide_banner -loglevel error \
-f concat -safe 0 -i temp_list.txt \
-c:v libx264 -crf $CRF_VALUE -preset medium \
-c:a aac -b:a "$AUDIO_BITRATE" \
-ar "$AUDIO_SAMPLERATE" \
-ac "$AUDIO_CHANNELS" \
-async 1000 \
-vsync cfr \
-y "$OUTPUT_FILE"
# ==============================================
# 5. 结果提示
# ==============================================
if [ $? -eq 0 ] && [ -f "$OUTPUT_FILE" ]; then
file_size=$(du -sh "$OUTPUT_FILE" | awk '{print $1}')
echo -e "\n\033[32m[+] 合并完成!\033[0m"
echo -e "文件:$(pwd)/$OUTPUT_FILE"
echo -e "大小:$file_size"
else
echo -e "\033[31m[!] 合并失败\033[0m"
exit 1
fi
public static function calcWeight($length, $width, $height, $num, $step = 1000000000)
{
$step = match($step) {
'吨', 'T', 't' => 1000000000,
'千克', 'KG', 'kg' => 1000000,
default => $step,
};
return floatval($length) * floatval($width) * floatval($height) * 7.85 * floatval($num) / $step;
}这段代码的重量计算逻辑基于物体的体积、密度和数量来计算总重量,具体公式和原理如下:
计算公式:
重量 = (长度 × 宽度 × 高度 × 密度 × 数量) ÷ 单位转换系数各参数说明:
length × width × height:计算物体的体积(假设单位是立方毫米,这是关键前提)7.85:这是密度值,单位是"克/立方厘米",常见于钢铁等金属材料的密度num:物体的数量(件数)step:单位转换系数,根据目标重量单位不同而变化
单位转换逻辑:
由于密度用的是克/立方厘米,而体积计算用的是立方毫米,需要进行单位换算:
所以从立方毫米到吨的转换需要除以1000000000(1000×1000×1000)
这个公式特别适合计算金属制品(如钢板、钢件)的重量,因为7.85是钢铁的密度值。如果计算其他材料的重量,需要替换7.85这个密度参数。