在 MySQL 5.7 及之前的版本,自增值保存在内存里,并没有持久化。每次重启后,第一次打开表的时候,都会去找自增值的最大值 max(id),然后将 max(id)+1 作为这个表当前的自增值
在 MySQL 8.0 版本,将自增值的变更记录在了 redo log 中,重启的时候依靠 redo log 恢复重启之前的值
自增值修改机制
如果插入数据时 id 字段指定为 0、null 或未指定值,那么就把这个表当前的 AUTO_INCREMENT 值填到自增字段
指定插入的主键值时,根据自增值生成算法计算新的自增值,影响参数: auto_increment_offset 、auto_increment_increment
自增值申请后未使用不允许回退
同一个语句多次申请自增id,每一次申请是前一次的两倍,可能造成浪费
MySQL 5.1.22 版本开始引入的参数 innodb_autoinc_lock_mode(默认1,语句结束后释放自增锁),控制了自增值申请时的锁范围。
默认值是 1。
0 :表示采用之前 MySQL 5.0 版本的策略,即语句执行结束后才释放锁
1 :普通 insert 语句,自增锁在申请之后就马上释放;类似 insert … select 这样的批量插入数据的语句,自增锁还是要等语句结束后才被释放。不包括普通的insert语句中包含多个value值的批量插入,因为可以计算需要多少个id,一次性申请后即可释放
2:申请后就释放锁(8.0默认值已改为2)
从并发性能的角度考虑,建议将其设置为 2,同时将 binlog_format 设置为 row
误删数据解决方案误删数据分类:
使用 delete 语句误删数据行
确保 binlog_format=row 和 binlog_row_image=FULL的前提下,使用Flashback工具闪回恢复数据
建议在从库上执行,避免对数据的二次破坏(数据变更是有关联的,有可能因为误操作的数据触发其他业务逻辑,从而导致其他数据的变更。因此,数据恢复需要再从库上进行,验证后再恢复回主库)
事前预防, sql_safe_updates=on关闭批量修改或删除,增加SQL审计
误删库/表:drop table 、truncate table、drop database
恢复方案:全量备份+实时备份binlog,可通过延迟复制备库优化,相当于一个最近可用的全量备份
预防方案:权限控制、制定操作规范(例如:先备份后删除,只能删除指定后缀表)
rm删除数据:高可用集群即可,HA机制会重新选择一个主库
insert ...select加锁分析 CREATE TABLE `t` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(null, 1,1); insert into t values(null, 2,2); insert into t values(null, 3,3); insert into t values(null, 4,4); create table t2 like t -- 语句1:不走索引,加锁范围:所有行锁和间隙锁 mysql> explain insert into t2(c,d) select c,d from t; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ | 1 | INSERT | t3 | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | | 1 | SIMPLE | t | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 100 | NULL | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+ -- 语句2:强制走索引c,倒序取第一条,加锁范围:(3,4]、(4,supremum] mysql> explain insert into t2(c,d) (select c+1, d from t force index(c) order by c desc limit 1); +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+---------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+---------------------+ | 1 | INSERT | t2 | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | | 1 | SIMPLE | t | NULL | index | NULL | c | 5 | NULL | 1 | 100 | Backward index scan | +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+---------------------+ -- 语句3:从表t查询数据,再插入到自身,需要暂存中间数据,使用了临时表,在临时表上limit, -- 加锁范围:所有行锁和间隙锁(8.0.11上和语句2一样,锁范围未发生变化) mysql> explain insert into t(c,d) (select c+1, d from t force index(c) order by c desc limit 1); +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------+ | 1 | INSERT | t | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | | 1 | SIMPLE | t | NULL | index | NULL | c | 5 | NULL | 1 | 100 | Backward index scan; Using temporary | +----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------+ -- 假设语句3,先把数据放入临时表,再进行limit,会扫描所有行,如何优化? create temporary table temp_t(c int,d int) engine=memory; insert into temp_t (select c+1, d from t force index(c) order by c desc limit 1); insert into t select * from temp_t; drop table temp_t;