redis避免大事务提交失败
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis避免大事务提交失败相关的知识,希望对你有一定的参考价值。
Redis事务,会将所有命令放入一个命令队列中,再由队列中执行命令。事务的命令分为:
MULTI 开始事务---->各种命令操作,SET,GET,SADD等等(每一步操作后,会显示QUEUED,表示已经入队命令队列)---->EXEC执行命令队列
DISCARD:取消一个事务,清空事务命令队列,客户端调整为非事务状态
WATCH:在进入事务之前执行,监听任意数量的key,当调用EXEC时,如果这个key被其他用户修改了,那么事务不在执行,返回失败。
以下示例展示了一个执行失败的事务例子:
redis> WATCH name
OK
redis> MULTI
OK
redis> SET name peter
QUEUED
redis> EXEC (nil)
以下执行序列展示了上面的例子是如何失败的:
时间 客户端 A 客户端 B
T1 WATCH name
T2 MULTI
T3SET name peter
T4 SET name john
T5EXEC
在时间 T4 ,客户端 B 修改了 name 键的值, 当客户端 A 在 T5 执行 EXEC 时,Redis 会发现 name 这个被监视的键已经被修改, 因此客户端 A 的事务不会被执行,而是直接返回失败。
ACID
Redis 的事务保证了 ACID 中的一致性(C)和隔离性(I),但并不保证原子性(A)和持久性(D)。
1.原子性(一个事务所有操作要么全部成功,要么全部失败)
redis的单个命令是原子性的,但是事务中的的redis命令,就算后面的命令失败/终止了(比如KILL进程,主机宕机等),此时事务失败,前面执行的命令也不会回滚。
2.一致性
(1)不正确入队命令的事务不会被执行,不会影响数据库的一致性
(2)命令执行中的错误,将错误包含在事务结果中,不会中断事务,不影响已执行结果,也不影响后面命令的执行,对事务的一致性也没有影响。
(3)进程终结
内存模式:如果 Redis 没有采取任何持久化机制,那么重启之后的数据库总是空白的,所以数据总是一致的。
RDB 模式:在执行事务时,Redis 不会中断事务去执行保存 RDB 的工作,只有在事务执行之后,保存 RDB 的工作才有可能开始。所以当 RDB 模式下的 Redis 服务器进程在事务中途被杀死时,事务内执行的命令,不管成功了多少,都不会被保存到 RDB 文件里。恢复数据库需要使用现有的 RDB 文件,而这个 RDB 文件的数据保存的是最近一次的数据库快照(snapshot),所以它的数据可能不是最新的,但只要 RDB 文件本身没有因为其他问题而出错,那么还原后的数据库就是一致的。
AOF 模式:因为保存 AOF 文件的工作在后台线程进行,所以即使是在事务执行的中途,保存 AOF 文件的工作也可以继续进行,因此,根据事务语句是否被写入并保存到 AOF 文件,有以下两种情况发生:
1)如果事务语句未写入到 AOF 文件,或 AOF 未被 SYNC 调用保存到磁盘,那么当进程被杀死之后,Redis 可以根据最近一次成功保存到磁盘的 AOF 文件来还原数据库,只要 AOF 文件本身没有因为其他问题而出错,那么还原后的数据库总是一致的,但其中的数据不一定是最新的。
2)如果事务的部分语句被写入到 AOF 文件,并且 AOF 文件被成功保存,那么不完整的事务执行信息就会遗留在 AOF 文件里,当重启 Redis 时,程序会检测到 AOF 文件并不完整,Redis 会退出,并报告错误。需要使用 redis-check-aof 工具将部分成功的事务命令移除之后,才能再次启动服务器。还原之后的数据总是一致的,而且数据也是最新的(直到事务执行之前为止)。
3.隔离性
Redis 是单进程程序,并且它保证在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止。因此,Redis 的事务是总是带有隔离性的。
4.持久性
由redis的持久化模式决定:
内存模式-无持久化
RDB-服务器可能在事务执行之后、RDB 文件更新之前的这段时间失败,所以 RDB 模式下的 Redis 事务也是不持久的。
AOF-后台每秒fsync策略,不是主线程阻塞执行,如果在保存时宕机,可能有一秒数据丢失,也非持久的。
发布于 4 年前著作权归作者所有

赞同 5

 参考技术A 答:避免失败操作:1.批量操作在发送EXEC命令前被放入队列缓存;
2.收到EXEC命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行;3.在事务执行过程中,其他客户端提交的命令请求不会插入到事务执行命令序列中……即可解决。 参考技术B 根据你提供的信息,要避免大事务提交失败,可以使用Redis数据库中的MULTI/EXEC功能来执行批量操作,保证原子性。
为什么要避免大事务以及大事务如何解决?
什么是大事务
运行时间比较长,长时间未提交的事务就可以称为大事务
大事务产生的原因
- 操作的数据比较多
- 大量的锁竞争
- 事务中有其他非DB的耗时操作
- 。。。
大事务造成的影响
- 并发情况下,数据库连接池容易被撑爆
- 锁定太多的数据,造成大量的阻塞和锁超时
- 执行时间长,容易造成主从延迟
- 回滚所需要的时间比较长
- undo log膨胀
- 。。。
如何查询大事务
注:本文的sql的操作都是基于mysql5.7版本
以查询执行时间超过10秒的事务为例:
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>10
如何避免大事务
通用解法
- 在一个事务里面, 避免一次处理太多数据
- 在一个事务里面,尽量避免不必要的查询
- 在一个事务里面, 避免耗时太多的操作,造成事务超时。一些非DB的操作,比如rpc调用,消息队列的操作尽量放到事务之外操作
基于mysql5.7的解法
- 在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
- 通过SETMAX_EXECUTION_TIME命令, 来控制每个语句查询的最长时间,避免单个语句意外查询太长时间
- 监控 information_schema.Innodb_trx表,设置长事务阈值,超过就报警/或者kill
- 在业务功能测试阶段要求输出所有的general_log,分析日志行为提前发现问题
- 设置innodb_undo_tablespaces值,将undo log分离到独立的表空间。如果真的出现大事务导致回滚段过大,这样设置后清理起来更方便
附录查询事务相关语句
注:sql语句都是基于mysql5.7版本
# 查询所有正在运行的事务及运行时间
select t.*,to_seconds(now())-to_seconds(t.trx_started) idle_time from INFORMATION_SCHEMA.INNODB_TRX t
# 查询事务详细信息及执行的SQL
select now(),(UNIX_TIMESTAMP(now()) - UNIX_TIMESTAMP(a.trx_started)) diff_sec,b.id,b.user,b.host,b.db,d.SQL_TEXT from information_schema.innodb_trx a inner join information_schema.PROCESSLIST b
on a.TRX_MYSQL_THREAD_ID=b.id and b.command = ‘Sleep‘
inner join performance_schema.threads c ON b.id = c.PROCESSLIST_ID
inner join performance_schema.events_statements_current d ON d.THREAD_ID = c.THREAD_ID;
# 查询事务执行过的所有历史SQL记录
SELECT
ps.id ‘PROCESS ID‘,
ps.USER,
ps.HOST,
esh.EVENT_ID,
trx.trx_started,
esh.event_name ‘EVENT NAME‘,
esh.sql_text ‘SQL‘,
ps.time
FROM
PERFORMANCE_SCHEMA.events_statements_history esh
JOIN PERFORMANCE_SCHEMA.threads th ON esh.thread_id = th.thread_id
JOIN information_schema.PROCESSLIST ps ON ps.id = th.processlist_id
LEFT JOIN information_schema.innodb_trx trx ON trx.trx_mysql_thread_id = ps.id
WHERE
trx.trx_id IS NOT NULL
AND ps.USER != ‘SYSTEM_USER‘
ORDER BY
esh.EVENT_ID;
# 简单查询事务锁
select * from sys.innodb_lock_waits
# 查询事务锁详细信息
SELECT
tmp.*,
c.SQL_Text blocking_sql_text,
p.HOST blocking_host
FROM
(
SELECT
r.trx_state wating_trx_state,
r.trx_id waiting_trx_id,
r.trx_mysql_thread_Id waiting_thread,
r.trx_query waiting_query,
b.trx_state blocking_trx_state,
b.trx_id blocking_trx_id,
b.trx_mysql_thread_id blocking_thread,
b.trx_query blocking_query
FROM
information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
) tmp,
information_schema.PROCESSLIST p,
PERFORMANCE_SCHEMA.events_statements_current c,
PERFORMANCE_SCHEMA.threads t
WHERE
tmp.blocking_thread = p.id
AND t.thread_id = c.THREAD_ID
AND t.PROCESSLIST_ID = p.id
参考
以上是关于redis避免大事务提交失败的主要内容,如果未能解决你的问题,请参考以下文章