Advertisement

insert时实现某一字段自增_MySQL auto_increment实现

阅读量:
65b1c12485c7204578ff0f574b830ab6.png

运维的时候,经常遇到auto_increment的疑惑:

  • 机器异常crash,重启后id回退的问题

性能考量方面,在每次获取操作时可能不会被持久化;内存中的数据直接用于操作;如何通过statement复制确保主备一致性?

  • id的取值受binlog的保护吗

1. auto_increment相关的参数控制

1.1 innodb_autoinc_lock_mode

每一个statement被分配一个exclusion lock,在该statement结束后停止分配新的lock,并确保在整个执行过程中每个statement都有唯一的id,并且这些id是连续分配的。

  • 在单个插入操作中确定受到影响的行数时,通过互斥锁实现。对于类似插入选择并加载数据的情况,则采用排他锁策略。

*多个statement产生的id会有重叠的情况出现;如果进行复制操作的话,则会导致数据不一致。

1.2

复制代码
    auto_increment_incrementauto_increment_offset控制自增的起始值和interval

2. auto_increment相关的数据结构

  • 锁模式中LOCK_AUTO_INC,即auto_increment的表锁。

复制代码
复制代码
    /* Basic lock modes */enum lock_mode {    LOCK_IS = 0,    /* intention shared */    LOCK_IX,    /* intention exclusive */    LOCK_S,        /* shared */    LOCK_X,        /* exclusive */    LOCK_AUTO_INC,    /* locks the auto-inc counter of a table in an exclusive mode */    LOCK_NONE,    /* this is used elsewhere to note consistent read */    LOCK_NUM = LOCK_NONE/* number of lock modes */};

``

  • dict_table_t:innodb表定义
复制代码
复制代码
    lock_t*        autoinc_lock;        表锁mutex_t        autoinc_mutex;         mutex锁ib_uint64_t    autoinc;            自增值ulong        n_waiting_or_granted_auto_inc_locks;  等待自增表锁的队列数const trx_t*        autoinc_trx;        hold自增表锁的事务

``

  • trx_t:事务结构
复制代码
复制代码
    ulint        n_autoinc_rows;        statement插入的行数ib_vector_t*    autoinc_locks;  持有的自增lock

``

  • handler:table的innodb引擎句柄
复制代码
复制代码
    ulonglong next_insert_id;   下次插入的idulonglong insert_id_for_cur_row; 当前插入的idDiscrete_interval auto_inc_interval_for_cur_row; 缓存,一次申请一个区间,缓存在server层。减少对innodb的调用uint auto_inc_intervals_count;  向innodb申请id的interval。按照[1, 2, 4, 8, 16]递增。最多1<<16 -1

``

注意:handler里的这些变量,只在一个语句下有效,语句结束就清理掉了。

3. 测试case

create table pp( id int primary key auto_increment, name varchar(100));

  • session1 : insert into pp(name) values('xx');

  • session2 : insert into pp(name) values('xx'),('xx'),('xx'),('xx')

  • session3 : insert into pp(name) select name from pp;

4. auto_increment的实现原理

cc240d4c6cfc676172dcb7ec340de944.png

4.2 锁的解释

根据锁持有的时间粒度,分为

  • 内存级别:类似mutex,很快释放

  • 语句级别:statement结束,释放

  • 事务级别:transaction提交或者回滚才释放

  • 会话级别:session级别,连接断开才释放

在本场景中,在处理数据插入时,默认情况下会将session1session2的数据明确指定数量,并通过互斥锁机制为每个会话分配固定的ID编号。而对于session3的情况,则由于其数据数量未知,在确保同一statement的所有插入操作具有连续且唯一的ID编号的前提下,在完成该statement的所有操作后才获取一个锁对象进行处理。

所以,为了提高并发量,锁持有的粒度越小越好。

4.3 缓存的解释

在某个statement中,预先分配id值以降低innodb的请求次数,并从而使得持有锁的数量相应减少。

5. 测试细节

5.1 第一次执行

根据select max(id) from pp:获取autoinc的初始值

这样也就解释了文章开头的第一个疑惑,为什么机器crash了,id会回退。

简单函数栈:

复制代码
    ha_innobase::open    innobase_initialize_autoinc

****

5.2 session 1

  • 首先 持有mutex,获取autoinc

由于插入的条数为1条,生成新的autoinc并更新至dict_table_t中后进行锁释放完成

简单函数栈

复制代码
    handler::update_auto_increment  ha_innobase::get_auto_increment    ha_innobase::innobase_lock_autoinc      mutex_enter(&table->autoinc_mutex);        dict_table_autoinc_update_if_greater

****

5.3 session 2

由于增加了4条insert数据后的情况,则前面所述步骤将与session1类似。在计算完成后生成新的autoinc值为5后会自动更新并保存到dict_table_t中

因为[3,4,5]被缓存之后

****

5.4 session 3

  • 因为不确定insert的条数,所以在语句的整个执行期间,持有lock。

  • 语句结束时,statement commit的时候释放

  • 第一次申请1个,第二次申请2个,第三次申请4个,共申请了3次。

简单函数栈:

复制代码
      handler::update_auto_increment    ha_innobase::get_auto_increment      row_lock_table_autoinc_for_mysql  trans_commit_stmt      row_unlock_table_autoinc_for_mysql

语句结束后, 清理语句级的环境

复制代码
      ha_release_auto_incrementinsert_id_for_cur_row= 0; 当前语句的insert id设置为0auto_inc_interval_for_cur_row.replace(0, 0, 0); 预分配的清空auto_inc_intervals_count= 0; 预分配的迭代数也清0table->in_use->auto_inc_intervals_forced.empty(); 清理链表

6. 警告:

如果你使用insert+delete模式管理你的表,在重启操作之后发现同一个id被重复使用,请注意这个潜在的问题

  • 如果表上有自增键,insert select,load file,会对insert产生阻塞。

7. 思考:

在分布式系统中如何构建一个全局唯一的递增机制(不一定要求连续性)?这也是分布式系统中一个需要解决的关键问题。

来源: http://www.cnblogs.com/xpchild/p/3825309.html

48a5171b1d72af93576d3b7773ef3478.png

全部评论 (0)

还没有任何评论哟~