MySQL优化-MySQL锁

MySQL优化-MySQL锁
    MySQL里都有什么锁
    MyISAM锁
    InnoDB锁
        锁类型
        InnoDB锁实现
        InnoDB锁案例
        InnoDB锁死锁
        InnoDB锁优化
    锁状态监控

锁的作用: 避免并发请求时对同一个数据对象同时修改,导致数据不一致。
怎么加锁:
    事务T1在对某个数据对象R1操作之前,先向系统发出请求,对其加锁L1.
    之后,事务T1对该数据对象R1有了相应的控制,在T1释放L1之前,其它事务不能修改R1.
锁类型:
    排它锁(X)
    共享锁(S)
通常的所范围:
    全局锁 (global lock)
    表锁   (table lock)
    行锁   (row lock)
innodb行锁范围(粒度):
    lock_rec_not_gap, record lock without gap lock
    lock_gap, gap lock
    lock_ordinary, next-key lock = record lock + gap lock
加锁对数据库的影响:
    A.锁等待,锁L1锁定某个对象R1,锁L2等待该锁释放,如果不释放,会一直等待,或者达到系统预设的超时阀值后告错(回滚整个事务或只回滚当前的SQL)
    B.死锁,锁资源请求产生了回路,例如: L1等待L2释放,L2等待L3释放,L3等待L1释放,死循环。
乐观锁、悲观锁
    对数据对象加锁不同"态度/方式"
        MGR、PXC用的就是乐观锁(事先不加锁,冲突检测过程中才加锁)
        select ... for update/for share就是悲观锁(提前加锁)


表锁:
    一般是在server层实现。
    innodb表,还有个IS/IX表级锁,以及auto-inc锁。
    读锁:
        lock table x read
        持有读锁的会话可以读表,但不能写表。
        允许多个会话同时持有读锁。
    写锁:
        lock table 表名 write
        持有写锁的会话即可以读表,也可以写表。
        只有持有写锁的会话才可以读写该表。
        其他会话访问该表或者请求加锁都会被阻塞,直到锁释放。
    释放锁:
        执行unlock tables
        执行lock table
        显式开启一个事务
        连接断开或者被kill
MyISAM锁:
    默认是表锁,读写互斥,仅只读共享。
    读锁,lock table user read, 自身只读,不能写;其他线程仍可读,不能写。多个线程都可提交read lock.
    写锁,lcok table user write, 自身可读写;其他线程完全不可读写。
    写锁优先级高于读锁。
    select自动加读锁(共享锁)
    其他DML, DDL自动加写锁(排他锁)
    释放锁: unlock tables/再次lock table/发起一个新事务。
innodb锁:
    默认是行锁(row lock),但也可以加表锁。
    innodb是通过在索引记录上加锁,实现行锁。
    因此,没有索引时就无法实现行锁,而升级成全表记录锁,等同于表锁。
    锁类型:
        共享锁
        排他锁
        意向锁,innodb特有,加载在表级别上的锁。
全局锁:
    global read lock (全局读锁)
        加锁: FTWRL, flush tables with read lock;
        关闭实例下的所有表,并加上全局读锁,防止被修改,直到提交unlock tables.
        一般用于备份,mysqldump, xtrabackup都会发起。
        xtrabackup时可分开备份InnoDB和MyISAM,或者不执行--master-data
        mysqldump --master-data=1 --single-transaction
        show processlist;
        select * from performance_schema.metadata_locks;
        如果都是InnoDB表,可以无需锁表在线热备。
    query cache lock (全局QC锁)
        全局query cache锁(mutex), 最好关闭query cache
        对QC中的数据有更新时,都会引发query cache lock
        状态: Waiting for query cache lock
        关闭query cache
            query_cache_type = 0 (实例启动前设置)
            query_cache_size = 0
        清空query cache:
            reset query cache;或者flush tables;
        8.0开始已禁用QC,8.0以前也建议关掉。
MDL锁:
    meta data lock
    5.5开始引入。
    tablespace/schema、表、function/procedure/trigger/event/等多种对象上加的锁。
    事务开启后,会锁定表的mata data lock, 其他会话对表有DDL操作时,均需等待MDL释放后方可继续。
    超时阀值定义: lock_wait_timeout.
    MDL锁类型:
        intention_exclusive     意外排他锁,只用于范围上锁
        shared                  共享锁,用于访问字典对象,而不访问数据
        shared_high_prio        只访问字典对象
        shared_read             共享读锁,用于读取数据
        shared_write            共享写锁,用于修改数据
        shared_no_write         共享非写锁,允许读取数据,阻塞其他TX读写数据
        shared_no_read_write    用于访问字典,读写数据,阻塞其他TX读写数据
        shared_read_only        只读锁
        exclusive               排他锁,可以修改字典和数据
    MDL锁观察:
        启用DML监测:
            在performance_schema库里
            update setup_consumers set enabled='YES' where name='global_instrumentation';
            update setup_instruments set enabled='YES' where name='wait/lock/metadata/sql/mdl';
        观察MDL锁:
            select * from performance_schema.matadata_locks\G
            select * from sys.schema_table_lock_waits\G
    即便是只读查询,或只读事务,也要尽快结束以释放MDL。
    利用sys schema观察MDL等待状态。
backup锁:   
    8.0新增功能,为了保证备份一致性,需要backup_admin权限。
    发起备份前,执行lock instance for backup, 备份结束后执行unlock instance解锁。
    backup lock的作用是备份期间依旧允许DML操作,以及session级的DDL操作,例如生成临时表。但是建表、改表、删表、repair、truncate、optimize都被禁止。
    多个会话可并行持有该锁。
自增锁:
    5.1之后新增innodb_autoinc_lock_mode选项
    当innodb_autoinc_lock_mode = 1时,其实是个轻量级的互斥量(MUTEX)
        1, 默认设置,快速mutext加锁,可预判行数时使用新方式,不可预判时仍旧使用表锁,会造成autoinc列自增空洞,不过影响很小。load data, insert ... select时,还用旧模式。
        0, 传统表级加锁模式,每次请求都会等待表锁,SQL结束后释放。可以保证主从时insert ... select一致性,但当有大insert时,并发效率很低。
        2, 使用新方式,不退化,不适合replication环境(可能造成主从数据不一致)。
    binlog_format=row时,可以放心地设置innodb_autoinc_lock_mode=2,降低自增锁的影响。
innodb自旋锁:
    InnoDB spin lock, 自旋锁
    保护共享资源而提出的一种锁机制,和互斥锁类似,任何时候下都只能有一个持有者,控制事务并发时的CPU时间片分配。
    用于控制innodb内部线程调度而生的轮询检测。
    innodb_spin_wait_delay, 控制轮训间隔,默认时6毫秒。
    事务并发非常高,CPU忙不过来的时候,事务处于sleep状态,spin round可能也会很高。
    可以利用自旋锁的状态来判断innodb线程内部争用严重与否。
    show engine innodb status\G
    Mutex spin waits 5870888, rounds 19812448, OS waits 375285.
InnoDB行锁实现机制
    基于索引实现,逐行检查,逐行加锁。
    没有索引的列上需要加锁时,会先对所有记录加锁,再根据实际情况决定是否释放锁。
    辅助索引上加锁时,同时要回溯到主键索引上再加一次锁。
    加锁的基本单位默认是lock_ordinary,当索引具有唯一性的时候退化为lock_rec_not_gap。
    (等值条件)逐行加锁时,会向右遍历到第一个不满足条件的记录,然后lock_ordinary退化为lock_gap。
    唯一索引的范围条件加锁时,也会对第一个不满足条件的记录加锁。
InnoDB行锁之共享锁
    共享锁,不允许其他事务修改被锁定的行,只能读。
    select ... for share/lock in share mode;
    自动提交模式下的普通select是一致性非锁定读,不加锁。
InnoDB行锁之排他锁
    对一行记录进行DML时,需至少加上排他锁。
    锁范围视情况而定,可能是record lock、next-key lock, 后者可能只有gap lock。
    执行DML,或select ... for update;
InnoDB锁之意向锁
    IS (intention shared), 事务T想要获得表中某几行的共享锁。
    IX (intention exclusive), 事务T想要获得表中某几行的排他锁。
    意向锁是加载在数据表B+树结构的根节点,也就是对整个表加意向锁。
    意向锁的作用:避免在执行DML时,对表执行DDL操作,导致数据不一致。
InnoDB行锁
    lock_ordinary
        著名的next-key lock, 锁住记录本身,及其前面的GAP。
        在RR级别下,利用next-key lock来避免产生幻读。
        当innodb_locks_unsafe_for_binlog=1时,lock_ordinary会降级为lock_rec_not_gap, 相当于降级到RC。
        注:innodb_locks_unsafe_for_binlog从MySQL 8.0后启用。
    lock_rec_not_gap
        仅记录锁,仅锁住记录本身,不锁其前面的GAP。
        RC下的行锁大多数都是这个锁类型。
        RR下的主键,唯一索引等值条件下加锁通常也是这个锁类型。
        RR下的非唯一索引加锁时 (lock_ordinary), 也会同时回溯到主键上加lock_rec_not_gap锁。但唯一性约束检测时,即使是在RC下,总是要先加lock_s|lock_ordinary锁。
    lock_gap
        间隙锁
        只锁住索引记录之间、或第一条索引记录 (infimum)之前、又或最后一条索引记录 (supremum)之后的范围,并不锁住记录本身。
        RR级别下,对非唯一索引记录当前读(current-read)时,除了对命中的记录加 lock_ordinary锁,还会对该记录之后的GAP加lock_gap, 这是为了保证可重复读的需要(避免其他事务插入数据,造成幻读)。
    lock_insert_intention
        插入意向锁,是一种特殊的gap lock, 当插入索引记录的时候用来判断是否有其他事务的范围锁冲突,如果有就需要等待。
        同一个GAP中,只要不是同一个位置就可以有多个插入意向锁并存。
        例如[5]~[10]区间中,同时插入[6][8]就不会相互冲突阻塞,而同时插入[9]就会引发冲突阻塞等待。
        但是lock_insert_intention和lock_gap并不兼容。
    RR级别 & 等值条件加锁
        主键索引是 lock_rec_not_gap
        唯一辅助索引是 lock_rec_not_gap
        普通辅助索引是 lock_ordinary
        没有索引的话,则是全表范围 lock_ordinary
    RC下,默认只有 lock_rec_not_gap, 只有在检查外键约束或者duplicate key检查时才加 lock_ordinary LOCK|S。
    RR & innodb_locks_unsafe_for_binlog = 1 (<=5.7), 同上效果。
InnoDB锁特点
    显式锁(explicit-lock)
        select * from t where id = ? for update/for share
    隐式锁(implicit-lock)
        update t set c2 = ? where c1 = ?
        任何辅助索引上锁,或非索引列上锁,都要回溯到主键上,也加锁。
        和其他session有冲突时,隐式锁转换为显示锁。
两种InnoDB读模式
    快照读,snapshot read.
        基于read view读可见版本,不加锁。
        start transaction with consistent read + select.
        普通select
    快照,read view
        由基于某个时间点的一组InnoDB内部(活跃)事务构建而成的列表。
    当前读,current read
        读(已提交的)最新版本,并加锁。
        S锁,select ... lock in share mode.
        X锁,select ... for update/DML.

什么是死锁,为什么会发生死锁,死锁可怕吗
如果多个事务都需要访问数据,而另一个事务已经以互斥方式锁定数据,则会发生死锁。
事务A等待事务B,同时事务B等待事务A,会产生死锁。
InnoDB有死锁检测线程,如果检索到死锁,会马上抛出异常并回滚一个事务(另一个继续执行)。
不同表也可以发生死锁。
show engine innodb status\G  只显示最后的死锁信息。
设置innodb_print_all_deadlocks = 1  在日志中记录全部死锁信息。
自动检测死锁,并自动回滚某个事物。优先回滚小事务,影响较小的事务,比如谁产生的undo更少。
高并发(秒杀)场景中,关闭innodb_deadlock_delect选项,降低死锁检测开销,提高并发效率。
表级锁不会发生死锁,但无法读写并发执行。
偶尔死锁不可怕,频繁死锁才需要关注。
程序中应有事务失败检测及自动重复提交机制。
多用小事务,并及时显示提交/回滚。
调整事务隔离级别为RC(消除gap lock),降低死锁发生概率。
事务中涉及多个表,或者涉及多行记录时,每个事务的操作顺序都要保持一致,降低死锁概率,最好用存储过程/存储函数固化。
通过索引优化SQL效率,降低死锁概率。
死锁不是"锁死",死锁会快速检测到,快速回滚。而"锁死"则是长时间锁等待。

常见SQL的锁模式
select ... from,一致性非锁定读,除非是serializable隔离级别(lock_ordinary | S)
lock in shared mode, lock_ordinary | S
for update, lock_ordinary | X
update/delete, lock_ordinary | X
普通insert,加锁lock_insert_intention | X,当写入请求检测到有重复值时,会加锁lock_ordinary | S,可能引发死锁。
insert ... on duplicate key update, lock_ordinary | X
备注:以上next-key lock只发生在RR隔离级别。

replace,没冲突/重复时,和insert一样(lock_insert_intention | X),否则lock_ordinary | X
insert into t select ... from s, t表加lock_rec_not_gap | X,事务隔离级别为RC或者启用innodb_locks_unsafe_for_binlog时,s表上采用无锁一致性读,否则s表加lock_ordinary | S(即:RC不加锁,RR加next-key lock)。
create table ... select, 同insert ... selectreplace into t select ... from s whereupdate t ... where col in (select ... from s ...),都会在s表上加lock_ordinary | S
auto_increment列上写新数据时,索引末尾加排他record lock。
请求自增列计数器时,innodb使用一个AUTO-INC mutex,但只对请求的那个SQL有影响(lock_mode = 1时)。
有外键约束字段上进行iud操作时,除了自身的锁,还会在外表约束列上同时加共享record lock。
备注:以上next-key lock只发生在RR隔离级别。

怎么确认锁等待怎么发生的,以及怎么解开这个锁等待,如何监控锁的影响、代价,以及预防锁影响扩大化(及时止损)。
查看innodb锁
show global status;
    innodb_row_lock_current_waits 当前等待的行锁数量。
    innodb_row_lock_time 请求行锁总耗时(ms)
    innodb_row_lock_time_avg 请求行锁平均耗时(ms)
    innodb_row_lock_time_max 请求行锁最久耗时(ms)
    innodb_row_lock_waits 行锁发生次数。

show processlist;
show engine innodb status;
sys var: innodb_status_output & innodb_status_output_locks
sys.innodb_lock_waits & sys.schema_table_lock_waits
    PFS.data_locks
    PFS.data_lock_waits
    PFS.metadata_locks

如何降低锁的影响,以及优化锁的效率。
InnoDB锁优化
    尽可能让所有的数据检索都通过索引来完成,从而避免InnoDB因为无法通过索引键加锁而升级为表级锁(全表所有行)。
    合理设计索引,让InnoDB在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他Query的执行。
    尽可能减少范围数据检索过滤条件,降低过多的数据被加上next-key lock。
    多使用primary key或unique key。

MySQL锁优化
    1.避免MyISAM,改用InnoDB。
    2.创建合适的索引,尽量不要多个单列索引。
    3.避免大事务,长事务。
    4.在业务低峰期DDL。
    5.执行DDL/备份前,先判断是否有长SQL,未提交事务,及其他的lock wait事件。

猜你喜欢

转载自www.cnblogs.com/zhouwanchun/p/12714667.html