1. 锁定义

锁是计算机协调多个进程或线程并发访问某一资源机制

在数据库中,除了传统的计算资源(如cpu,ram,I/O)的争用外,数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。

2.锁分类

. 从性能上分为乐观锁和悲观锁

  1. 乐观锁:采用版本verison 来对比实现,不涉及等待。
  2. 悲观锁:有数据进行修改等操作,先锁住,其他用户不能进行额外的操作,涉及等待。

. 从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁)

读锁(共享锁): 针对同一份数据,多个读操作可以同时进行而不会互相影响

写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁

. 从对数据操作的粒度分,分为表锁和行锁 (页锁 innodb不支持)

2.1 表锁

每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

2.1.1 手动增加表锁

  1. lock table 表名称 read(write);

2.1.2 查看表上加过的锁

  1. show open tables;

2.1.3 删除表锁

  1. unlock tables;

分析(MyISAM存储引擎):

  1. lock table mylock read;

当前session和其他session都可以读该表

当前session中插入或者更新锁定的表都会报错,其他session插入或更新则会等待

  1. lock table mylock write

当前session对该表的增删改查都没有问题,其他session对该表的所有操作被阻塞

总结:

MyISAM在执行查询语句(select前),会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁

  1. 对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞同一表的写请求。只有当读锁释放后,才会执行其他进程的写操作。
  2. 对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作
    即,读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞。

2.2 行锁

每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。

InnoDB特点(与MyISAM的区别):

. 支持事务(TRANSACTION)

. 支持行级锁

2.2.1 行锁支持事务

. 事务(Transaction)及其ACID属性

事务是由一组sql组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。

. 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

. 一致性(Consistent): 在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。

. 隔离性(Isolation): 数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的。

. 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即便出现系统故障也能够保持。

并发事务处理带来的问题:

  1. 更新丢失(Lost Update)
    当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题-最后的更新覆盖了由其他事务所做的更新。
  2. 脏读(Dirty Reads)
    一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取到这个‘脏’数据,并据此作进一步的处理,就会产生未提交的数据依赖关系,这种现象叫做“脏读”。
    即:事务A读取到事务B已经修改但尚未提交的数据,还在这个数据的基础上做了操作。此时,如果事务B回滚,A读到的数据无效,不符合一致性的要求。
  3. 不可重复读(Non-Repeatable Reads)
    一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变,或某些记录已经被删除了!这种现象叫做“不可重复读”。
    即:事务A读到了事务B已经提交的修改数据,不符合隔离性!
  4. 幻读(Phantom Reads)
    一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象称为“幻读”。
    即:事务A读取到了事务B提交的新增数据,不符合隔离性!

2.2.2 事务隔离级别

脏读,不可重复读,幻读,其实都是数据库读一致性的问题,必须由数据库提供一定的事务隔离级别来解决。
image-20210723112013486.png
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”有矛盾。

同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

查看事务隔离级别:

  1. show variables like 'tx_isolation';

设置事务隔离级别:

  1. set tx_isolation='REPEATABLE-READ';

mysql默认的事务隔离级别为:可重复读 ;

mysql普通执行一条sql,默认为自动提交;

行锁演示:

一个session开启事务更新不提交,另一个session更新同一条记录会阻塞,更新不同记录不会阻塞。

问题:

. mysql默认的事务隔离级别为:可重复读 ,有办法解决幻读问题?

间隙锁在某些情况下可以解决幻读问题。

要避免幻读可以用间隙锁在session_1下午执行 update account set name=’xx’ where id>18 and id<20;则其他Session没法在这个范围所包含的间隙里插入或修改任何数据。

. 无索引行锁会升级为表锁:锁主要是加在索引上,如果对非索引字段更新,行锁可能会变表锁

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁。

. 锁定某一行还可以用lock in share mode(共享锁)和 for update(排它锁)

  1. select * from employees for update; 这样其他的session只能读到这行数据,修改则会被阻塞,直到锁定行的session提交

总结:

InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一下,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的,当系统并发量高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势了。但是,InnoDB的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。

2.2.3 行锁分析

通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况

  1. show status like 'innodb_row_lock%';

image-20210723170650932.png
Innodb_row_lock_current_waits:当前正在等待锁定的数量

Innodb_row_lock_time:从系统启动到现在锁定总时间长度

Innodb_row_lock_time_avg:每次等待所花平均时间

Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花时间

Innodb_row_lock_waits:系统启动后到现在总共等待的次数

2.2.4 死锁

查看近期死锁日志信息

  1. show engine innodb status\G;

大多数情况mysql可以自动检测死锁并且回滚产生死锁的那个事务,但是有些情况下mysql没法自动检测死锁

2.2.5 优化

. 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁

. 合理设计索引,尽量缩小锁的范围

. 尽可能减少检索条件范围,避免间隙锁

. 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行

. 尽可能使用低级别事务隔离