根据锁的范围划分,在MySQL中可以分为全局锁、表级锁和行锁三类
全局锁顾名思义就是对整个数据库实例加锁,全局锁的典型使用场景就是做全局的逻辑备份,备份过程中整个库处于只读状态
使整个库处于只读状态可以执行命令flush tables with read lock
(FTWRL),执行这个命令后其他线程执行数据更新语句(数据的增删改) 、数据定义语句(包括建表、修改表结构) 时都会被阻塞
另一种方式是使用官方自带的逻辑备份工具mysqldump
使用-- single transacation
参数,原理是备份前开启一个事务拿到一致性视图,根据事务的隔离级别,在可重复读级别下这个视图中读的数据总是与开启事务时一致。single transacation
只适用于所有表都使用的事务引擎的库
在MySQL里表级锁有表锁和元数据锁(meta data lock)MDL
表锁的语法是:
-- 加锁
lock tables tableName1 read/write,tableName2 read/write ... ;
-- 释放锁
unlock tables
举一个例子,如果一个线程A执行lock tables t1 read,t2 write
之后,其他线程只能读t1,写t1和读写t2都会被阻塞。A线程也只能读t1和读写t2,其他表不能访问
当使用lock tables
控制并发时锁住了整个表,从而带来了严重的性能问题,InnoDB支持行锁一般就不使用lock tables
控制并发了,将lock tables和unlock tables
改成begin和commit
MDL是在访问表时自动加上,不需要显示指定。MDL的作用是保证数据读写的正确性。当一个线程查询出一个列表数据时,同时另一个线程删除了一个字段,这样造成数据不一致显然这种情况时不能接受的
当对一个表进行增删改时加MDL读锁,当改变表结构时加MDL写锁
当我们需要跟一个表添加字段时,首先要解决长事务,因为事务会一直占着MDL锁直到事务提交才会释放,当这个表是热点表又需要在这个表上加字段,比较理想的机制是在执行alter table
语句里增加等待时间,MariaDB引擎已经实现
alter table t1 nowait add column ...
alter table t2 wait add column ...
顾明思意行锁就是数据表中行的锁,当两个事务A,B同时更新同一行时,只能等事务A执行完之后事务B才能去执行更新操作
InnoDB的行锁是针对索引加的锁,不是针对记录的锁,并且索引不能失效,否则会从行锁升级成表锁
共享锁:
select * from tableName where ... lock in share more
排他锁:select * from tableName where ... for update
首先我们先了解一下两阶段锁协议,在InnoDB事务中,行锁是在需要的时候在加上,但并不是在不需要的时候则释放,需要等到事务提交之后才释放。这个就是两阶段锁协议
了解这个协议之后,我们使用事务时应该注意,当一个事务需要多个锁时,把最有可能造成冲突,最有可能影响并发度的锁应该尽量往后放
共享锁也称读锁,多用于判断数据是否存在
排他锁也称读写锁,独占锁,当前操作没有执行完之前,会阻塞其他的读锁和写锁
当执行INSERT,UPDATE,DELETE时,InnoDB会自动加排他锁
当出现并发时不同的线程出现循环依赖资源,涉及到的线程都在等待其他线程释放资源,从而造成这些线程出现无限等待的状态,这种情况称为死锁。当出现死锁状态时有两种处理策略:
innodb_lock_wait_timeout
来设置,默认是50sinnodb_deadlock_detect=on
,表示死锁检测开启怎么解决热点行更新造成的性能问题
当死锁检测开启时,当并发数达到1000同时去更新同一条数据时,检测死锁这个操作就是100W级别的,造成CPU负载很高,那么每秒执行的事务也很低。怎么去解决这种性能问题了
关闭死锁检测
。业务中如果出现死锁时就回滚然后重试这样是无损的,当然死锁也会出现大量的超时,这就对业务有损了控制并发度
,如在客户端控制更新同一行的并发度,当客户端多时服务端的并发度也随之增加。当然可以修改数据库的服务端进行控制,对于更新相同行时排队进入引擎,这样InnoDB内部就不会有那么高的死锁检测了,一般小团队没有能力做到从表设计上优化
,可以通过更新一行的操作改成逻辑上的多行来减少冲突。如在库存表中,一个商品的库存可以放在10条记录中,商品实际库存为几条记录之和,这样每次操作库存发生冲突的概率为原来的1/10,减少了锁的等待个数,也就减少了死锁检测带来的CPU消耗。这种方案就需要控制数据的边界了参考文献