1、MySQL锁的基本介绍

mysql常用引擎有MYISAM和InnoDB,而InnoDB是mysql默认的引擎。MYISAM不支持行锁,而InnoDB支持行锁和表锁。

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的 计算资源(如CPU、RAM、I/O等)的以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一 个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。

相对其他数据库而言,MySQL的锁机制比较简单,其最 显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAMMEMORY存储引擎采用的是表级锁(table-level locking)InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!仅从锁的角度 来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有 并发查询的应用,如一些在线事务处理(OLTP)系统。

2、数据处理大致可以分成两大类:

  1. OLTP:联机事务处理OLTP(on-line transaction processing)
    ,OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。
  2. OLAP:联机分析处理OLAP(On-Line Analytical Processing),OLAP是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。

OLTP 系统强调数据库内存效率,强调内存各种指标的命令率,强调绑定变量,强调并发操作;
OLAP 系统则强调数据分析,强调SQL执行市场,强调磁盘I/O,强调分区等。

OLTP与OLAP的特点

mysql默认什么锁 mysql默认锁机制_数据

OLTP

事务性非常高的系统,一般是高可用的在线系统,以小的事务以及小的查询为主,评估其系统的时候,一般看其每秒执行的Transaction以及Execute SQL的数量。在这样的系统中,单个数据库每秒处理的Transaction往往超过几百个,或者是几千个,Select 语句的执行量每秒几千甚至几万个。典型的OLTP系统有电子商务系统、银行、证券等。

OLAP

也叫DSS决策支持系统,就是我们说的数据仓库。在这样的系统中,语句的执行量不是考核标准,因为一条语句的执行时间可能会非常长,读取的数据也非常多。所以,在这样的系统中,考核的标准往往是磁盘子系统的吞吐量(带宽),如能达到多少MB/s的流量。

3、MyISAM表锁

MySQL的表级锁有两种模式:

表共享读锁(Table Read Lock):

  • 用户A给B表加读锁不会阻塞其他用户对B表的读请求,但会阻塞对B表的写请求

表独占写锁(Table Write Lock):

  • 用户A给B表加写锁,则会阻塞其他用户对B表的读和写操作

MyISAM表的读操作与写操作之间,以及写操作之间是串行的!

通俗讲表共享读锁是按摩店的妹子,表独占写锁是自己的妹子,按摩妹子可以和你谈价钱的时候(读操作),也可以同时接受别人谈价钱(读请求)。但是按摩妹子和你做游戏的时候(写操作),别人就无法和按摩妹子谈价钱(读请求)和做游戏了(写请求)。

举个栗子:写锁

3.1)此案例在阿里云服务器上的mysql上建表

CREATE TABLE `mylock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `NAME` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `mylock` (`id`, `NAME`) VALUES ('1', 'a');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('2', 'b');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('3', 'c');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('4', 'd');

查看mysql引擎是MyISAM

mysql> show create table mylock;
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table  | Create Table                                                                                                                                                                  |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mylock | CREATE TABLE `mylock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `NAME` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>

3.2)分别打开xshell和dos命令行窗口连接好远程mysql

当一个线程对一个表加写锁之后,只有持有锁的线程可以对表进行更新操作。其他线程的读写操作都会等待,直到锁释放为止。

用户A

其他用户

给表加上write锁

lock table mylock write;

用户A对表查询、插入、更新操作都可以

select * from mylock;

insert into mylock values(5,‘e’);

其他用户对表的查询会被阻塞

select * from mylock;

释放锁:

unlock tables;

其他用户能够立刻执行,并返回对应结果

3.3)演示

a)用户A给mylock表添加写锁后,就可以对mylock表做增删改查等任何操作
mysql> lock table mylock write;  #加锁
Query OK, 0 rows affected (0.01 sec)

mysql> select * from mylock;   #查询操作
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
+----+------+
4 rows in set (0.01 sec)

mysql> insert into mylock(name) values("pi");  #增加操作
Query OK, 1 row affected (0.07 sec)

mysql> select *from mylock;
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
+----+------+
5 rows in set (0.01 sec)

mysql>
b)当用户A给mylock表添加写锁期间,其他用户就不能对mylock表做任何操作,包括查询也不可以

此时用户B对mylock表发出读请求后,用户B迟迟收不到反馈,一直阻塞中

mysql> select * from mylock;

# 下面是空白,查询不出任何数据,一直处于请求读状态
c)当用户A给mylock表解锁后
mysql> lock table mylock write;  #上锁
Query OK, 0 rows affected (0.06 sec)

mysql> select * from mylock; #操作
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
+----+------+
5 rows in set (0.01 sec)

mysql> unlock tables;  #开锁
Query OK, 0 rows affected (0.01 sec)

mysql>

其他用户才会收到mylock表的回复

mysql> select * from mylock;  #当用户A给mylock表开锁后
+----+------+                 #其他用户才可以收到mylock表的回复
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
+----+------+
5 rows in set (23.38 sec)  #其他用户等待用户A和mylock表做游戏的时间

mysql>

举个栗子:读锁

用户A使用lock table给表加读锁,用户A可以查询mylock表的记录,但增删改包括对其他表做增删改查等任何操作都会提示错误,其他用户可以查询mylock表中的记录,但对mylock表做增删改操作就会出现锁等待。

用户A

其他用户

给表加read锁

lock table mylock read;

当前session可以查询该表记录:

select * from mylock;

其他用户可以查询mylock表:

select * from mylock;

用户A不能查询没有锁定的其他表

select * from emp

Table ‘emp’ was not locked with LOCK TABLES

其他用户可以查询或者更新未锁定的表

select * from mylock

insert into person values(1,‘zhangsan’);

用户A插入或者更新表会提示错误

insert into mylock values(6,‘f’)

Table ‘mylock’ was locked with a READ lock and can’t be updated

update mylock set name=‘aa’ where id = 1;

Table ‘mylock’ was locked with a READ lock and can’t be updated

其他用户对mylock表做增删改操作会等待,直到获得锁

insert into mylock values(6,‘f’);

释放锁

unlock tables;

获得锁,更新成功

a)用户A给mylock表加读锁后,只能对mylock表做读操作,不能对mylock表做增删改等操作,也不能对其他表做增删改查等任何操作

mysql> lock table mylock read;  #加读锁
Query OK, 0 rows affected (0.07 sec)

mysql> select *from mylock;  #可查询
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
+----+------+
5 rows in set (0.01 sec)

mysql> insert into mylock(name) values("dan"); #不能做增删改操作
ERROR 1099 (HY000): Table 'mylock' was locked with a READ lock and can't be updated
mysql> select * from emp;   #不能对其他表做增删改查等任何操作
ERROR 1100 (HY000): Table 'emp' was not locked with LOCK TABLES
mysql> unlock tables;
Query OK, 0 rows affected (0.02 sec)

mysql>

b)在mylock表被用户A加读锁期间,其他用户对mylock表只能做查询操作,除此不能做其他任何操作。

mysql> select * from mylock;
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
+----+------+
5 rows in set (0.00 sec)

mysql>

c)当用户A给mylock表开锁后,所有用户就又可以对mylock做任何操作了。

读锁小结

A爱上了B婊,订了婚,A可以和B经常在床上彻夜长谈(查询),但按伦理道德来说,没结婚后,A不能和B做其他游戏(增删改操作),而且已经是准新郎了,就不能再和其他妹子彻夜在酒店长谈(查询),并且做游戏了(增删改);

MyISAM在执行查询语句之前,会自动给涉及的所有表加读锁,在执行更新操作前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要使用命令来显式加锁,上例中的加锁时为了演示效果。

问题:MyISAM的并发插入问题

MyISAM表的读和写是串行的,这是就总体而言的,在一定条件下,MyISAM也支持查询和插入操作的并发执行

用户A

用户B

获取表的read local锁定

lock table mylock read local

当前session不能对表进行更新或者插入操作

insert into mylock values(6,‘f’)

Table ‘mylock’ was locked with a READ lock and can’t be updated

update mylock set name=‘aa’ where id = 1;

Table ‘mylock’ was locked with a READ lock and can’t be updated

其他session可以查询该表的记录

select* from mylock

用户A不能查询没有锁定的表

select * from person

Table ‘person’ was not locked with LOCK TABLES

其他用户可以进行插入操作,但是更新会阻塞

update mylock set name = ‘aa’ where id = 1;

用户A不能获取到其他用户插入的记录;

释放锁资源:unlock tables

其他用户获取锁,更新操作完成

用户A可以查询到其他用户插入的记录

可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺:

mysql> show status like '%table%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Table_locks_immediate | 352   |
| Table_locks_waited    | 2     |
+-----------------------+-------+
--如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况。

举个栗子:MyISAM的并发插入

默认允许并发插入

mysql> show variables like '%current%';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| concurrent_insert | AUTO  |
+-------------------+-------+
1 row in set (0.01 sec)

mysql>

老婆在银行查你小金库,atm机显示余额100元(自动加了读锁),这时你正好偷偷的又转账到银行账户10元(插入操作),atm机子显示的余额不会变,还是只显示100元(加锁期间用户A不能查询到其他用户插入的记录),如果老婆不放心,退卡重新又查了一次,余额就会变为110元(释放锁资源,用户A可以查询到其他用户插入的记录)。

a)用户A查询时,自动加了读锁,用户A只能查询,不能对本表做增删改等其他操作。也不能对其他表做任何操作。

mysql> lock table mylock read local;
Query OK, 0 rows affected (0.02 sec)

mysql> select * from mylock; #用户A只能对mylock表做查询操作
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
+----+------+
5 rows in set (0.02 sec)

mysql> insert into mylock(name) values("dan"); #其他操作都不能
ERROR 1099 (HY000): Table 'mylock' was locked with a READ lock and can't be updated

b)用户B可以对mylock表做查询操作,也可以做增删改操作,但是用户A在读加锁期间是无法查询获取到的

mysql> select * from mylock;
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
+----+------+
5 rows in set (0.00 sec)

mysql> insert into mylock(name) values("xian");
Query OK, 1 row affected (0.01 sec)

mysql> select * from mylock;
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
|  6 | xian |
+----+------+
6 rows in set (0.00 sec)

mysql>

c) 用户A开锁后,再次查询才可获取到用户B修改后的数据

mysql> select * from mylock;  #加锁前查询不到其他用户对表修改后的数据
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
+----+------+
5 rows in set (0.01 sec)

mysql> unlock tables;  #开锁
Query OK, 0 rows affected (0.02 sec)

mysql> select * from mylock; #开锁后获取到其他用户对表修改后的数据
+----+------+
| id | NAME |
+----+------+
|  1 | a    |
|  2 | b    |
|  3 | c    |
|  4 | d    |
|  5 | pi   |
|  6 | xian |
+----+------+
6 rows in set (0.03 sec)

mysql>

4、InnoDB锁

1、事务及其ACID属性

事务是由一组SQL语句组成的逻辑处理单元,事务具有4属性,通常称为事务的ACID属性。

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

2、并发事务带来的问题

相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多用户的并发操作,但与此同时,会带来一下问题:

  • 脏读: 一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”
  • 不可重复读:一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。
  • 幻读: 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”

上述出现的问题都是数据库读一致性的问题,可以通过事务的隔离机制来进行保证。

数据库的事务隔离越严格,并发副作用就越小,但付出的代价也就越大,因为事务隔离本质上就是使事务在一定程度上串行化,需要根据具体的业务需求来决定使用哪种隔离级别

脏读

不可重复读

幻读

read uncommitted




read committed



repeatable read


serializable

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

mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 18702 |
| Innodb_row_lock_time_avg      | 18702 |
| Innodb_row_lock_time_max      | 18702 |
| Innodb_row_lock_waits         | 1     |
+-------------------------------+-------+
--如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高

3、InnoDB的行锁模式及加锁方法

  • 共享锁(s):又称读锁。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
  • 排他锁(x):又称写锁。允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。

mysql InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

4、InnoDB行锁实现方式

InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

1、在不通过索引条件查询的时候,innodb使用的是表锁而不是行锁

create table tab_no_index(id int,name varchar(10)) engine=innodb;
insert into tab_no_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');

用户A

用户B

set autocommit=0

select * from tab_no_index where id = 1;

set autocommit=0

select * from tab_no_index where id =2

select * from tab_no_index where id = 1 for update

select * from tab_no_index where id = 2 for update;

用户A只对一行加了排他锁,但是用户B在请求其他行的排他锁时,会出现锁等待。原因是在没有索引的情况下,innodb只能使用表锁

举个栗子

1)设置事务为非自动提交

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)

mysql> set autocommit=0;  #设置事务不自动提交
Query OK, 0 rows affected (0.00 sec) #set global autocommit=0;

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

mysql>

2)建表且不加主键等索引

用户A只有手动提交事务后

mysql> create table tab_no_index(   #建表
    -> id int(10),
    -> name varchar(10))
    -> engine = innodb;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into tab_no_index(id,name) values(1,"1"),(2,"2"),(3,"3"),(4,"4");
Query OK, 4 rows affected (0.00 sec)  #添加数据
Records: 4  Duplicates: 0  Warnings: 0

mysql> commit;    #手动提交事务
Query OK, 0 rows affected (0.01 sec)

mysql>

其他用户才可以查询到数据

mysql> select * from tab_no_index; #用户A提交事务前,其他用户查询不到
Empty set (0.03 sec)
mysql> select * from tab_no_index; #用户A提交事务后,其他用户可查询到
+------+------+
| id   | name |
+------+------+
|    1 | 1    |
|    2 | 2    |
|    3 | 3    |
|    4 | 4    |
+------+------+
4 rows in set (0.02 sec)

mysql>

3)用户A为表tab_no_index的其中一行加写锁

mysql> select * from tab_no_index where id = 1 for update;
+------+------+
| id   | name |
+------+------+
|    1 | 1    |
+------+------+
1 row in set (0.00 sec)  #for update代表是写锁

mysql>

4)因为innodb规则是没有索引就代表是加的表锁,而不是行锁,所以其他用户对tab_no_index表就无法再做行锁操作了

mysql> select * from tab_no_index where id =2 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql>

2、创建带索引的表进行条件查询,innodb使用的是行锁

create table tab_with_index(id int,name varchar(10)) engine=innodb;
alter table tab_with_index add index id(id);
insert into tab_with_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');

session1

session2

set autocommit=0

select * from tab_with_indexwhere id = 1;

set autocommit=0

select * from tab_with_indexwhere id =2

select * from tab_with_indexwhere id = 1 for update

select * from tab_with_indexwhere id = 2 for update;

举个栗子

1、建表
mysql> create table tab_with_index (
    -> id int(10) primary key,  #加主键索引
    -> name varchar(10))
    -> engine=innodb;  #加innodb引擎
Query OK, 0 rows affected (0.01 sec)

mysql> insert into tab_with_index(id,name) values(1,"1"),(2,"2"),(3,"3"),(4,"4");
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> select * from tab_with_index;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
|  2 | 2    |
|  3 | 3    |
|  4 | 4    |
+----+------+
4 rows in set (0.00 sec)
2、设置事务为手动提交
mysql> select @@autocommit;  #设置是set autocommit=0;
+--------------+
| @@autocommit |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

mysql>
3、查询表中索引
mysql> show index from tab_with_index;
+----------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table          | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| tab_with_index |          0 | PRIMARY  |            1 | id          | A         |           4 |     NULL | NULL   |      | BTREE      |         |               |
+----------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
1 row in set (0.00 sec)

mysql> commit;  #手动提交事务
Query OK, 0 rows affected (0.00 sec)
4、加写锁

为用户A加写锁,锁住id=1

mysql> select * from tab_with_index where id = 1 for update;
+----+------+
| id | name |
+----+------+
|  1 | 1    |
+----+------+
1 row in set (0.00 sec)

mysql> update tab_with_index set name='dan' where id=2;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
mysql> select *from tab_with_index where id = 2; #写锁是可以查询的
+----+------+
| id | name |
+----+------+
|  2 | 2    |
+----+------+
1 row in set (0.00 sec)

mysql>

为用户B加写锁,锁住id=2

mysql> select * from tab_with_index where id = 2 for update;
+----+------+
| id | name |
+----+------+
|  2 | 2    |
+----+------+
1 row in set (0.02 sec)

mysql> update tab_with_index set name='pi' where id =1;
#因为用户A第id=1的行加了写锁,所以用户B修改加写锁的行需要等待锁

小结:写锁 for update

用户A对tab_with_index表中id=1的行做了加写锁操作,用户B对tab_with_index表中id=2的行做了加写锁操作,这样用户A和B就不能对双方进行增删改操作,但是互相可以做查询操作。

5、加读锁
mysql> select * from tab_with_index where id=1 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql>

小结:读锁 lock in share mode

当tab_with_index表中id=1的行加了写锁后,如果此时再对此行加读锁的话,就会发生读等待;

3、由于mysql的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是依然无法访问到具体的数据

insert into tab_with_index  values(1,'4');

session1

session2

set autocommit=0

set autocommit=0

select * from tab_with_index where id = 1 and name=‘1’ for update

select * from tab_with_index where id = 1 and name=‘4’ for update

虽然session2访问的是和session1不同的记录,但是因为使用了相同的索引,所以需要等待锁

总结

MyISAM的表锁

  1. 共享读锁(S)之间是兼容的,但共享读锁(S)与排他写锁(X)之间,以及排他写锁(X)之间是互斥的,也就是说读和写是串行的。
  2. 在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表查询和插入的锁争用问题。
  3. MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。
  4. 由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新操作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突。

InnoDB表

  1. InnoDB的行锁是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。
  2. 在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。

在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:

  • 尽量使用较低的隔离级别; 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;
  • 选择合理的事务大小,小事务发生锁冲突的几率也更小;
  • 给记录集显式加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;
  • 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;
  • 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响; 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
  • 对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。