MySQL锁

文章目录

  • 前言
  • 一、概述
  • 二、MyISAM表锁
  • 三、InnoDB锁问题
    • 1.事务
      • 1)事务属性
      • 2)事务相关语法
    • 2.行锁模式
    • 3.行锁使用常见问题
  • 总结


前言

本文主要介绍了锁的基本概念,以及InnoDB引擎使用行锁时的注意事项


一、概述

锁只要是用来解决数据访问的一致性、有效性问题。
锁可以按照下面两种维度区分
按照对数据操作粒度区分:

锁类型 描述
表锁 操作时,锁定整张表,偏向MyISAM 存储引擎,开销小,加锁块;
不会出现死锁;锁粒度大,发生锁冲突的概率最高,并发度最小
行级锁 操作时,锁定当前操作行;偏向InnoDB引擎,开销大加锁慢;会出现死锁
锁粒度最小,发生锁冲突的概率最低,并发度最高
页面锁 开销和加锁时间介于表锁和行锁之间;会出现死锁;锁粒度介于表锁和行锁之间

从对数据操作的类型分:
1) 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
2) 写锁(排它锁):当前操作没有完成之前,它会阻断其他写锁和读锁。

二、MyISAM表锁

MyISAM 存储引擎只支持表锁,这也是MySQL开始几个版本中唯一支持的锁类型。

MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。

对MyISAM表的读操作,读锁跟写锁有如下特点:

  • 读操作不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求。
  • 写操作,会阻塞其他用户对同一表的读和写操作。

数据:

mysql> CREATE TABLE `tb_book` (
    -> `id` INT(11) auto_increment,
    -> `name` VARCHAR(50) DEFAULT NULL,
    -> `publish_time` DATE DEFAULT NULL,
    -> `status` CHAR(1) DEFAULT NULL,
    -> PRIMARY KEY (`id`)
    -> ) ENGINE=myisam DEFAULT CHARSET=utf8 ;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'java编程思想','2088-08-01','1');
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'solr编程思想','2088-08-08','0');
Query OK, 1 row affected (0.00 sec)

mysql> CREATE TABLE `tb_user` (
    -> `id` INT(11) auto_increment,
    -> `name` VARCHAR(50) DEFAULT NULL,
    -> PRIMARY KEY (`id`)
    -> ) ENGINE=myisam DEFAULT CHARSET=utf8 ;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO tb_user (id, name) VALUES(NULL,'令狐冲');
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO tb_user (id, name) VALUES(NULL,'田伯光');
Query OK, 1 row affected (0.00 sec)

显示加锁命令:

-- 读锁
lock table table_name read;
-- 写锁
lock table table_name write

写锁演示:

session1 session2
获得表tb_user写锁
mysql> lock table tb_user write;
Query OK, 0 rows affected (0.00 sec)
当前session对锁定的表的查询、更新、插入都可以执行
mysql> select * from tb_user;
+----+-----------+
| id | name      |
+----+-----------+
|  1 | 令狐冲    |
|  2 | 田伯光    |
|  3 | 任盈盈    |
+----+-----------+

mysql> insert into tb_user values(null,'风清杨');
Query OK, 1 row affected (0.00 sec

mysql> update tb_user set name='东方不败' where id=3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
其他session对锁定表的查询被阻塞,需要等待锁被释放
mysql> select * from tb_user;
等待
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
等待
session2获得锁,查询返回
mysql> select * from tb_user;
+----+--------------+
| id | name         |
+----+--------------+
|  1 | 令狐冲       |
|  2 | 田伯光       |
|  3 | 东方不败     |
|  4 | 风清杨       |
+----+--------------+
4 rows in set (1 min 50.20 sec)

读锁展示:

session1 session2
获得表tb_user读锁
mysql> lock table tb_user read;
Query OK, 0 rows affected (0.00 sec)
当前session可以查询锁定的表
mysql> SELECT * FROM tb_user;
+----+--------------+
| id | name         |
+----+--------------+
|  1 | 令狐冲       |
|  2 | 田伯光       |
|  3 | 东方不败     |
|  4 | 风清杨       |
+----+--------------+
4 rows in set (0.00 sec)
当前session不能查询没有锁定的表
mysql> SELECT * FROM tb_book;
ERROR 1100 (HY000): Table 'tb_book' was not locked with LOCK TABLES
其他session可以查询锁定表的记录
mysql> select * from tb_user;
+----+--------------+
| id | name         |
+----+--------------+
|  1 | 令狐冲       |
|  2 | 田伯光       |
|  3 | 东方不败     |
|  4 | 风清杨       |
+----+--------------+
4 rows in set (0.00 sec)

其他session可以查询或更新为锁定的表
mysql> SELECT id,status from tb_book;
+----+--------+
| id | status |
+----+--------+
|  1 | 1      |
|  2 | 0      |
+----+--------+
2 rows in set (0.00 sec)
mysql> update tb_book set status=1 where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
等待

当前session插入或更新锁定的表都会发生错误
mysql> INSERT INTO tb_user values(null,'仪琳');
ERROR 1099 (HY000): Table 'tb_user' was locked with a READ lock and can't be updated
mysql> UPDATE tb_user SET name='仪琳' WHERE id=3;
ERROR 1099 (HY000): Table 'tb_user' was locked with a READ lock and can't be updated
其他session更新锁定表会处于等待
mysql>  UPDATE tb_user SET name='仪琳' WHERE id=3;
等待
释放锁
mysql> unlock table;
Query OK, 0 rows affected (0.00 sec)
等待
session获得锁,更新操作完成
mysql>  UPDATE tb_user SET name='仪琳' WHERE id=3;
Query OK, 1 row affected (1 min 13.69 sec)
Rows matched: 1  Changed: 1  Warnings: 0

三、InnoDB锁问题

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。

1.事务

1)事务属性

事务是由一组SQL语句组成的逻辑处理单元,具有以下ACID属性:

  • 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全部成功,要么全部失败。
  • 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。
  • 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的 “独立” 环境下运行。
  • 持久性(Durable):事务完成后,对于数据的修改是永久的。

并发事务处理带来的问题

问题 描述
丢失更新
(Lost Update)
当两个或多个事务选择同一行,最初的事务修改的值,
会后面的事务修改的值覆盖。
脏读
(Dirty Reads)
当一个事务正在访问数据,并且对数据进行了修改,
而这种修改还没有提交到数据库中,这时,另外一个事务也访问到修改了但没提交数据
不可重复读
(Non Repeatable Reads)
在同一个事务内,两次读到的数据不一致
幻读
(Phantom Reads)
一个事务按照相同的查询条件重新读取以前查询过的数据,
却发现其他事务插入了 满足其查询条件的新数据。

事务隔离级别
为了解决上述提到的事务并发问题,数据库提供一定的事务隔离机制

隔离级别 丢失更新 脏读 不可重复读 幻读
Read uncommitted ×
Read committed × ×
Repeatable read(默认) × × ×
Serializable × × × ×

备注 : √ 代表可能出现 , × 代表不会出现 。

2)事务相关语法

查看隔离级别

mysql> SHOW variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.01 sec)

查询事务状态(是否自动提交)

-- 查询事务状态(是否自动提交)
mysql> SHOW VARIABLES LIKE 'AUTOCOMMIT';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)
-- 设置事务不自动提交
mysql> SET AUTOCOMMIT=0;
-- 设置事务隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 开启事务
START TRANSACTION;
-- 手动提交
COMMIT;

行锁等待时间

mysql> SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set (0.02 sec)

2.行锁模式

InnoDB实现了以下两种类型的锁模式:

  • 共享锁(S):又称为读锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
  • 排他锁(X):又称为写锁,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);
对于普通SELECT语句,InnoDB不会加任何锁;
可以通过以下语法显示给记录集加共享锁或排他锁

-- 共享锁(S)
SELECT * FROM tb_name WHERE ... LOCK IN SHARE MODE;
-- 排他锁(X)
SELECT * FROM tb_name WHERE ... FOR UPDATE

下面演示锁:
数据准备

create table innodb_lock(
	id int(11),
	name varchar(16),
	sex varchar(1)
)engine = innodb default charset=utf8;
insert into innodb_lock values(1,'100','1');
insert into innodb_lock values(3,'3','1');
insert into innodb_lock values(4,'400','0');
insert into innodb_lock values(5,'500','1');
insert into innodb_lock values(6,'600','0');
insert into innodb_lock values(7,'700','0');
insert into innodb_lock values(8,'800','1');
insert into innodb_lock values(9,'900','1');
insert into innodb_lock values(1,'200','0');
create index idx_lock_id on innodb_lock(id);
create index idx_name on innodb_lock(name);

1)共享锁

session1 session2
关闭自动提交
MySQL锁_第1张图片
关闭自动提交
MySQL锁_第2张图片
当前session对id为1的记录加share mode共享锁
MySQL锁_第3张图片
其他session仍然可以查询该条记录,并添加共享锁
MySQL锁_第4张图片
当前session对锁定的记录进行更新操作,等待锁
在这里插入图片描述
其他session对锁定数据进行更新
在这里插入图片描述
获得锁后,该session成功更新

2)排他锁

session1 session2
MySQL锁_第5张图片 MySQL锁_第6张图片
对id为1的记录加排他锁
MySQL锁_第7张图片
其他session可以查询该记录,但是不能对该记录加共享锁,会处于等待
MySQL锁_第8张图片
当前记录可以对锁定的数据进行更新
MySQL锁_第9张图片
其他session获得锁,并查询到session1提交的记录
MySQL锁_第10张图片

3.行锁使用常见问题

InnoDB行锁是通过给索引上的索引项加锁实现的,如果没有索引,将通过隐藏的聚簇索引来对记录加锁。这种行锁的实现特点意味着,如果不通过索引条件检索数据,将对表中的所有记录加锁,实际效果跟表锁一样。
InnoDB行锁分3种情形:

  • Record lock:对索引项加锁
  • Gap lock:对索引项之间的“间隙”、第一条记录前的“间隙”或最后一条记录后的“间隙”加锁
  • Next-key lock:前两种组合,对记录及前面间隙加锁。

1)在不同过索引条件查询时,InnoDB会锁定表中的所有记录

mysql> drop index idx_lock_id on innodb_lock;
Query OK, 0 rows affected (0.02 sec)
mysql> SHOW CREATE TABLE innodb_lock\G
*************************** 1. row ***************************
       Table: innodb_lock
Create Table: CREATE TABLE `innodb_lock` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(16) DEFAULT NULL,
  `sex` varchar(1) DEFAULT NULL,
  KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
session1 session2
MySQL锁_第11张图片 MySQL锁_第12张图片
对id=1的记录进行更新,但未提交
在这里插入图片描述
session2对id=3的记录更新,处于锁等待状态
在这里插入图片描述
session1提交,释放锁
在这里插入图片描述
session2获得锁后,更新成功
在这里插入图片描述

作为对比,可对id进行添加索引后,在进行上面的更新操作。这里不再演示
需要特别注意的是,虽然建立了索引,查询索引失效了(如传值类型不匹配),就会导致对索引的记录进行加锁。

小结:在没有使用索引的情况下更新,InnoDB会对所有的记录都加锁

2)访问不同行记录,相同的索引键,会出现锁冲突

mysql> CREATE  index idx_id ON innodb_lock(id);
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> drop index idx_name ON innodb_lock;
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE innodb_lock\G
*************************** 1. row ***************************
       Table: innodb_lock
Create Table: CREATE TABLE `innodb_lock` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(16) DEFAULT NULL,
  `sex` varchar(1) DEFAULT NULL,
  KEY `idx_id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> SELECT * FROM innodb_lock WHERE id=1;
+------+------+------+
| id   | name | sex  |
+------+------+------+
|    1 | 100  | 0    |
|    1 | 200  | 0    |
+------+------+------+

数据id为1的记录符合“访问不同行记录,相同的索引键”

session session
在这里插入图片描述 在这里插入图片描述
MySQL锁_第13张图片
虽然session2访问的是个session_1不同的记录 ,但是因为使用了相同的索引,所以需要等待锁
在这里插入图片描述

3)有多个索引时,InnoDB会对多个索引都加锁

mysql> ALTER TABLE innodb_lock ADD INDEX idx_name(name);
Query OK, 0 rows affected (0.03 sec)
mysql> SHOW CREATE TABLE innodb_lock\G
*************************** 1. row ***************************
       Table: innodb_lock
Create Table: CREATE TABLE `innodb_lock` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(16) DEFAULT NULL,
  `sex` varchar(1) DEFAULT NULL,
  KEY `idx_id` (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> SELECT * FROM innodb_lock WHERE id = 1;
+------+------+------+
| id   | name | sex  |
+------+------+------+
|    1 | 100  | 0    |
|    1 | 200  | 0    |
+------+------+------+
session1 session2
在这里插入图片描述 在这里插入图片描述
MySQL锁_第14张图片
session2使用name的索引访问记录,因为记录没有被加锁,所以可以获得锁
MySQL锁_第15张图片
访问的记录被session1锁定,等待获得锁
在这里插入图片描述

4)Next-Key锁
当我们用范围条件,而不是使用相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据进行加锁; 对于键值在条件范围内但并不存在的记录,叫做 “间隙(GAP)” , InnoDB也会对这个 “间隙” 加锁,这种锁机制就是所谓的 间隙锁(Next-Key锁) 。
举例说明:emp表中有101条记录,其empId的值分别为1,2,…,100,101
下面的sql
SELECT * FROM emp WHERE empId>100 FOR UPDATE
是一个范围条件的检索,InnoDB不仅会对符合条件的empId为101的记录加锁,也会对empId大于101(这些记录不存在)的“间隙”加锁。

mysql> SELECT * FROM innodb_lock;
+------+------+------+
| id   | name | sex  |
+------+------+------+
|    1 | 100  | 0    |
|    3 | 3    | 0    |
|    4 | 400  | 0    |
|    5 | 500  | 1    |
|    6 | 600  | 0    |
|    7 | 700  | 0    |
|    8 | 800  | 1    |
|    9 | 900  | 1    |
|    1 | 200  | 0    |
+------+------+------+
9 rows in set (0.00 sec)
session1 session2
在这里插入图片描述 在这里插入图片描述
根据id范围更新数据
在这里插入图片描述
插入id=2的记录,处于阻塞状态
在这里插入图片描述
提交事务
在这里插入图片描述
阻塞解除,插入成功
在这里插入图片描述

总结

当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势。
当我们使用InnoDB的行级锁不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
优化建议:

  • 尽可能让所有数据检索都能通过索引来完成,避免无索引行锁升级为表锁。
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能减少索引条件,及索引范围,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度

你可能感兴趣的:(MySQL,mysql,数据库锁)