关于MySQL变量innodb_rollback_on_timeout一些讨论

探讨MySQL InnoDB存储引擎中innodb_rollback_on_timeout参数的作用,分析其如何影响事务的回滚行为,特别是在加锁超时情况下的表现。

作者:MrDB 

出处:http://www.cnblogs.com/hustcat/ 

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


关于MySQL变量innodb_rollback_on_timeout一些讨论

1innodb_rollback_on_timeout变量

下面是MySQL官方手册关开innodb_rollback_on_timeout变量的说明:

In MySQL 5.0.13 and up, InnoDB rolls back only the last statement on a transaction timeout by default. If --innodb_rollback_on_timeout is specified, a transaction timeout causes InnoDB to abort and roll back the entire transaction (the same behavior as before MySQL 5.0.13). This variable was added in MySQL 5.0.32.

该变量默认值为OFF,如果事务因为加锁超时,会回滚上一条语句执行的操作。如果设置ON,则整个事务都会回滚。

 

下面通过一个示例来验证上面这段话。

2、示例

(1) innodb_rollback_on_timeoutOFF

Session 1

Session 2

mysql> create table tt(c1 int primary key, c2 int)engine=innodb;

Query OK, 0 rows affected (0.01 sec)

mysql> insert into tt values(1, 1);

Query OK, 1 row affected (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from tt where c1=1 lock in share mode;

+----+------+

| c1 | c2   |

+----+------+

|  1 |    1 |

+----+------+

1 row in set (0.00 sec)

 

 

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

 

mysql> insert into tt values(10,10);

Query OK, 1 row affected (0.00 sec)

 

mysql> delete from tt where c1=1;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql> select * from tt;

+----+------+

| c1 | c2   |

+----+------+

|  1 |    1 |

| 10 |   10 |

+----+------+

2 rows in set (0.00 sec)

 

mysql> rollback;

Query OK, 0 rows affected (0.01 sec)

 

mysql> select * from tt;

+----+------+

| c1 | c2   |

+----+------+

|  1 |    1 |

+----+------+

1 row in set (0.00 sec)

mysql> select * from tt;

+----+------+

| c1 | c2   |

+----+------+

|  1 |    1 |

+----+------+

1 row in set (0.00 sec)

 

 

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

 

mysql> insert into tt values(10,10);

Query OK, 1 row affected (0.00 sec)

 

mysql> delete from tt where c1=1;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql> commit;

Query OK, 0 rows affected (0.02 sec)

 

mysql> select * from tt;

+----+------+

| c1 | c2   |

+----+------+

|  1 |    1 |

| 10 |   10 |

+----+------+

2 rows in set (0.00 sec)

session2因为加锁超时,事务回退到上一条语句。 

 

(2) innodb_rollback_on_timeoutON

Session 1

Session 2

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

 

mysql> select * from tt where c1=1 lock in share mode;

+----+------+

| c1 | c2   |

+----+------+

|  1 |    1 |

+----+------+

1 row in set (0.00 sec)

 

 

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

 

mysql> insert into tt values(11,11);

Query OK, 1 row affected (0.00 sec)

 

mysql> delete from tt where c1=1;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql> select * from tt;

+----+------+

| c1 | c2   |

+----+------+

|  1 |    1 |

| 10 |   10 |

+----+------+

2 rows in set (0.00 sec)

mysql> commit;

Query OK, 0 rows affected (0.00 sec)

 

mysql> select * from tt;

+----+------+

| c1 | c2   |

+----+------+

|  1 |    1 |

| 10 |   10 |

+----+------+

2 rows in set (0.00 sec)

session2加锁超时,整个事务回滚。

 

3、总结

innodb_rollback_on_timeoutOFF,事务会回滚到上一个保存点,InnoDB在执行每条SQL语句之前,都会创建一个保存点,参见代码:

int

row_insert_for_mysql(

                                               /* out: error code or DB_SUCCESS */

         byte*                 mysql_rec,       /* in: row in the MySQL format */

         row_prebuilt_t*     prebuilt)  /* in: prebuilt struct in MySQL

                                               handle */

{

。。。

         savept = trx_savept_take(trx);

。。。

如果事务因为加锁超时,相当于回滚到上一条语句。但是报错后,事务还没有完成,用户可以选择是继续提交,或者回滚之前的操作,由用户选择是否进一步提交或者回滚事务。

innodb_rollback_on_timeoutON,整个事务都会回滚。这可以从row_mysql_handle_errors函数中得到验证。

复制代码
ibool
row_mysql_handle_errors(
/* ==================== */
                 /*  out: TRUE if it was a lock wait and
                we should continue running the query thread 
*/
    ulint*        new_err, /*  out: possible new error encountered in
                lock wait, or if no new error, the value
                of trx->error_state at the entry of this
                function 
*/
    trx_t*        trx,     /*  in: transaction  */
    que_thr_t*    thr,     /*  in: query thread  */
    trx_savept_t*    savept)     /*  in: savepoint or NULL  */
{
...
else  if (err == DB_DEADLOCK  // 发生死锁
           || err == DB_LOCK_TABLE_FULL
           || (err == DB_LOCK_WAIT_TIMEOUT
               && row_rollback_on_timeout)) {
         /*  Roll back the whole transaction; this resolution was added
        to version 3.23.43 
*/

        trx_general_rollback_for_mysql(trx, FALSE, NULL);  // 事务全部回滚
                
    }  else  if (err == DB_OUT_OF_FILE_SPACE
           || err == DB_LOCK_WAIT_TIMEOUT) {

        ut_ad(!(err == DB_LOCK_WAIT_TIMEOUT
                && row_rollback_on_timeout));

                if (savept) {  // 回滚到上一个保存点
             /*  Roll back the latest, possibly incomplete
            insertion or update 
*/

            trx_general_rollback_for_mysql(trx, TRUE, savept);
        }
         /*  MySQL will roll back the latest SQL statement  */
...
复制代码

 

问题:innodb_rollback_on_timeout为OFF,事务的原子性被破坏了吗?

答:NO,从示例中可以看到,事务只是回退上一条语句的状态,而整个事务实际上没有完成(提交或者回滚),而作为应用程序在检测这个错误时,应该选择是提交或者回滚事务。如果严格要求事务的原子性,当然是执行ROLLBACK,回滚事务。

 

一键安装mysql脚本#!/bin/bash# # 要用root用户 ## #获取本机的ip地址,然后将这个ip地址设置为环境变量ipaddr的值 export ipaddr=`ip a | grep -w inet | grep -v 127| awk '{ print $2 }' |awk -F '/' '{print $1}'` #将本机的ip地址的第三第四位合在一起,然后赋予给环境变量serverid,加入本机ip为10.192.168.178,那么serverid=168178 export serverid=`echo $ipaddr | awk -F '.' '{print $3$4}'` #以下这一段是为了求得机器的内存是多少。dmidecode -t 17是获取系统内存相关信息,并把这个值赋予给MEM_SIZE这个环境变量 export UNIT=`dmidecode -t 17|grep -i size |grep -i GB|wc -l` if [[ $UNIT -eq 0 ]]; then export MEM_SIZE=`dmidecode -t 17|grep -i size |grep -v 'No'|awk '{sum+=$2;}END{print sum;}'` else export SIZE=`dmidecode -t 17|grep -i size |grep -v 'No'|awk '{sum+=$2;}END{print sum;}'` export MEM_SIZE=$(($SIZE*1024)) fi # 物理内存等于虚拟内存除以2,并把这个值赋予给buffer这个环境变量 export buffer=$(($MEM_SIZE/2)) # 密码需要修改 export pwd=yourpassword export rplpwd=yourpassword #统计一下 系统中装有mariadb-libs的数量 mariadbcheck=`rpm -qa|grep mariadb-libs|wc -l` #如果统计值后发现mariadb-libs的数量等于1,说明系统里装了mariadb-libs,那么我就要用yum remove删除他 if [[ $mariadbcheck -eq 1 ]]; then yum remove -y mariadb-libs fi #解压/opt 目录下的 mysql-8.0.13-linux-glibc2.12-x86_64.tar.xz 文件,并将解压过程中的输出信息重定向到 /dev/null ,这意味着不会在终端显示解压的详细过程和输出。 #tar 是用于打包和解包文件的命令。-J 表示使用 xz 格式进行解压缩。-x 表示解包。-v 表示显示详细信息。-f 后跟要操作的文件。 tar -Jxvf /opt/mysql-8.0.13-linux-glibc2.12-x86_64.tar.xz >/dev/null mv /opt/mysql-8.0.13-linux-glibc2.12-x86_64/* /usr/local/mysql/ chown -R mysql:mysql /usr/local/mysql/ cp /usr/local/mysql/bin/* /usr/bin/ #检查是否存在/etc/my.cnf个文件,如果存在那么就以当天日期重命名这个文件然后备份为bak,例如my.cnf.20240905140424.bak if [ -s /etc/my.cnf ]; then mv /etc/my.cnf /etc/my.cnf.`date +%Y%m%d%H%M%S`.bak fi #创建一个新的/etc/my.cnf文件,然后将以下内容写进去,直至遇到EOF这个标识符,EOF之后的内容就别写进去了 cat >/etc/my.cnf <<EOF [mysqld] read_only = 0 show_compatibility_56 = 1 port = 3306 server_id = $serverid user = mysql basedir = /usr/local/mysql datadir = /vdb/mysql/data socket = /vdb/mysql/tmp/mysql.sock tmpdir = /vdb/mysql/tmp character_set_server = utf8mb4 transaction_isolation = READ-COMMITTED event_scheduler = 1 default-time-zone = "+8:00" log_timestamps = SYSTEM explicit_defaults_for_timestamp = 1 secure_file_priv = "" skip_slave_start = 1 skip_name_resolve = 1 skip_external_locking = 1 lower_case_table_names = 1 default_storage_engine = InnoDB disabled_storage_engines = ARCHIVE,BLACKHOLE,EXAMPLE,FEDERATED,MEMORY,MERGE,NDB # connection # lock_wait_timeout = 1800 max_connections = 3000 max_connect_errors = 1000000 interactive_timeout = 1800 wait_timeout = 1800 # session memory setting # read_buffer_size = 8M read_rnd_buffer_size = 8M sort_buffer_size = 2M tmp_table_size = 64M join_buffer_size = 64M max_heap_table_size = 64M max_allowed_packet = 64M # cache config # key_buffer_size = 16M table_definition_cache = 2000 table_open_cache = 2000 table_open_cache_instances = 8 query_cache_type = 0 query_cache_size = 0 thread_cache_size = 200 open_files_limit = 65536 binlog_cache_size = 1M max_binlog_cache_size = 512M # log config # log_bin = /vdb/mysql/binlog/mysql-bin binlog_format = row sync_binlog = 1 binlog_error_action = ABORT_SERVER max_binlog_size = 250M binlog_rows_query_log_events = 1 expire_logs_days = 7 log_bin_trust_function_creators = 1 log_slave_updates = 1 relay_log = /vdb/mysql/binlog/mysql-relay relay_log_recovery = 1 master_info_repository = TABLE relay_log_info_repository = TABLE log_error = /vdb/mysql/logs/error.log slow_query_log = 1 slow_query_log_file = /vdb/mysql/logs/slow.log long_query_time = 5 log_queries_not_using_indexes = 1 log_slow_admin_statements = 1 log_slow_slave_statements = 1 log_throttle_queries_not_using_indexes = 10 min_examined_row_limit = 100 # innodb settings # innodb_data_home_dir = /vdb/mysql/data innodb_log_group_home_dir = /vdb/mysql/data innodb_file_per_table = 1 innodb_data_file_path = ibdata1:1G:autoextend innodb_flush_log_at_trx_commit = 1 innodb_buffer_pool_size = ${buffer}M innodb_buffer_pool_instances = 8 innodb_buffer_pool_load_at_startup = 1 innodb_buffer_pool_dump_at_shutdown = 1 innodb_buffer_pool_dump_pct = 25 innodb_lock_wait_timeout = 10 innodb_io_capacity = 10000 innodb_io_capacity_max = 20000 innodb_max_dirty_pages_pct = 60 innodb_flush_method = O_DIRECT innodb_log_file_size = 2G innodb_log_files_in_group = 2 innodb_log_buffer_size = 64M innodb_purge_threads = 2 innodb_write_io_threads = 16 innodb_read_io_threads = 16 innodb_large_prefix = 1 innodb_thread_concurrency = 64 innodb_print_all_deadlocks = 1 innodb_strict_mode = 1 innodb_sort_buffer_size = 32M innodb_stats_persistent_sample_pages = 64 innodb_autoinc_lock_mode = 2 innodb_online_alter_log_max_size = 1G innodb_open_files = 4096 innodb_temp_data_file_path = ibtmp1:12M:autoextend:max:20G # undo config # innodb_rollback_segments = 128 innodb_undo_log_truncate = 1 innodb_max_undo_log_size = 4G # GTID # gtid_mode = ON enforce_gtid_consistency = 1 binlog_gtid_simple_recovery = 1 # MTS # slave_parallel_type = LOGICAL_CLOCK slave_parallel_workers = 16 slave_preserve_commit_order = ON slave_rows_search_algorithms = 'INDEX_SCAN,HASH_SCAN' # SEMISYNC # plugin-load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so" rpl_semi_sync_master_enabled = 1 rpl_semi_sync_slave_enabled = 1 rpl_semi_sync_master_timeout = 60000 #1min rpl_semi_sync_master_wait_for_slave_count = 1 rpl_semi_sync_master_wait_no_slave = 0 # Performance Schema Config # performance-schema-instrument = 'wait/lock/metadata/sql/mdl=ON' performance-schema-instrument = 'memory/%=COUNTED' # Other # innodb_numa_interleave = 1 [mysqldump] quick max_allowed_packet = 2G log-error = /vdb/mysql/logs/dump.log net_buffer_length = 8K [mysqladmin] default-character-set = utf8mb4 socket = /vdb/mysql/tmp/mysql.sock [client] port = 3306 socket = /vdb/mysql/tmp/mysql.sock [mysql] prompt = [\\u@\\h][\\d]:\\_ default-character-set = utf8mb4 no-auto-rehash EOF #这里用的是>>,是追加新内容到/root/.bash_profile,并不是全覆盖。 #然后将以下内容写进去,直至遇到EOF这个标识符,EOF之后的内容就别写进去了 cat >>/root/.bash_profile <<EOF export PATH=/usr/local/mysql/bin:\$PATH EOF 刷新/root/.bash_profile这个文件并使之生效 source /root/.bash_profile #这段命令用于启动 MySQL 服务器,并指定了一些重要的参数: #/usr/local/mysql/bin/mysqld这个是mysql的启动项,相当于windows桌面上的启动图标双击一下就运行该软件了 # --defaults-file=/etc/my.cnf:指定了配置文件的路径。 #--user=mysql:指定以 mysql 用户身份运行服务器。 #--datadir=/vdb/mysql/data:指定数据存储的目录。 #--basedir=/usr/local/mysql:指定 MySQL 的安装目录。 #--initialize-insecure:执行不安全的初始化,这可能意味着设置一些默认的、不太严格的安全选项来快速初始化数据库。 /usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --user=mysql --datadir=/vdb/mysql/data --basedir=/usr/local/mysql --initialize-insecure #把/usr/local/mysql/support-files/mysql.server复制到/etc/init.d/这个文件夹里面去,取名为mysqld #/etc/init.d/ 文件夹通常用于存放系统服务和守护进程的启动、停止、重启等操作的脚本。 #也就是说我们这样手动安装的软件mysql,以后也可以用systemctl start mysql这样的命令来启动mysql了比较方便。 # /usr/local/mysql/support-files/mysql.server这个文件其实就是一个程序脚本,你看路径名里都有一个support-files #就是为了方便后续用户可以添加到linux系统里面的快捷启动方式里面(systemctl)。 #但是上面那个/usr/local/mysql/bin/mysqld是mysql的启动文件,这个不是一个脚本,用vim是打不开的,看不到里有啥内容 cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld #赋予/etc/init.d/mysqld具有可执行的权限 chmod 755 /etc/init.d/mysqld #将名为 mysqld 的服务添加到系统的服务管理配置中,方便可以用systemctl来控制启动、停止等状态 chkconfig --add mysqld #设置 MySQL 服务(mysqld)在运行级别 2、3、4、5 中为开机自启动(on)状态。 #运行级别是系统在不同模式下的运行状态,例如,2 通常是多用户模式但没有网络,3 是多用户模式带网络,4 通常未被使用,5 是带有图形界面的多用户模式。 chkconfig --level 2345 mysqld on #将”/usr/local/mysql/lib“写入到/etc/ld.so.conf.d/mysql-x86_64.conf里面去 cat >> /etc/ld.so.conf.d/mysql-x86_64.conf<<EOF /usr/local/mysql/lib EOF #刷新系统的共享库缓存,以便程序能够正确找到和加载所需的共享库 ldconfig #用于检查/proc/vz是否为一个目录,条件为真,就会执行then后面的语句 #ulimit -s unlimited 这条命令用于设置栈大小(stack size)为无限制(unlimited)。 #在某些情况下,如果程序需要较大的栈空间来处理复杂的操作或递归调用,可能会使用这条命令来避免栈空间不足导致的错误。 if [ -d "/proc/vz" ]; then ulimit -s unlimited fi #使用systemctl来启动mysql systemctl start mysqld.service # 创建监控用户,只读用户,复制用户 /usr/local/mysql/bin/mysqladmin -u root password $pwd > /dev/dull #把这两句SQL语句写入到/tmp/mysql_sec_scripts里面去 #这段 SQL 语句的作用是授予用户 'repl_user'@'%' 复制从服务器的权限。 #*.* 表示在所有数据库和所有表上授予权限。 #identified by '$rplpwd' 表示设置该用户的密码为 $rplpwd 变量所代表的值。 #flush privileges是刷新授权表; cat > /tmp/mysql_sec_scripts<<EOF grant replication slave on *.* to 'repl_user'@'%' identified by '$rplpwd'; flush privileges; EOF # 使用 /usr/local/mysql/bin/mysql 这个 MySQL 客户端程序, #以 root 用户身份,密码由 $pwd 变量指定,连接到本地主机(localhost)上的 MySQL 服务器 #并执行 /tmp/mysql_sec_scripts 文件中的 SQL 语句 # /usr/local/mysql/bin/mysql和刚才上面提到的/usr/local/mysql/bin/mysqld是不太一样的 # mysql是客户端程序,mysqld是主程序,主程序是运行在背后,但是我们人类想看mysql数据库里面的各种数据库具体表格 #就需要用到mysql客户端了,用mysql客户端连接背后的数据库用SQL语句进行相关的增删改查交互操作 /usr/local/mysql/bin/mysql -u root -p$pwd -h localhost </tmp/mysql_sec_scripts #删除/tmp/mysql_sec_scripts rm -f /tmp/mysql_sec_scripts #检查MySQL主程序有没有运行,在运行就赋值mcheck为1,否则为0 mcheck=`systemctl status mysqld.service|grep 'active (running)'|wc -l` #如果/usr/local/mysql/bin/mysql存在,并且/usr/local/mysql/bin/mysqld_safe也存在, #并且/etc/my.cnf也存在,并且MySQL主程序在运行中 #那么输出MySQL 5.7 install completed,以及MySQL网络连接状态信息,不然就输出安装失败Failed #ss -anutlp | grep 3306就是使用 ss 命令查看所有的网络连接状态信息; #(包括套接字的类型、状态、本地地址和端口、远程地址和端口等),然后通过 grep 3306 筛选出与端口 3306 相关的连接信息。 if [ -s /usr/local/mysql/bin/mysql ] && [ -s /usr/local/mysql/bin/mysqld_safe ] && [ -s /etc/my.cnf ] && [ $mcheck -eq 1 ]; then echo "" echo "MySQL 5.7 install completed" ss -anutlp | grep 3306 else echo "Error: MySQL Install Failed!!" fi
最新发布
08-06
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值