关于mysql中的锁总结

一、锁的基本信息:

共享锁(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…查询数据,因为普通查询没有任何锁机制。

意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者两者不兼容,该事务就要等待锁释放。

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁

下面语句都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)

select * from table where ? lock in share mode;

select * from table where ? for update;

insert into table values (…);

update table set ? where ?;

delete from table where ?;

二、行锁与索引:

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

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

(2)由于MySQL的行锁是针对索引加的锁(主键是自动加了索引的,但加了索引的字段不是主键,所以加了索引但不是主键的字段上的数据是可以重复的),不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。

(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。

(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决 定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突 时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

比如,在tab_with_index表里的name字段有索引,但是name字段是varchar类型的,检索值的数据类型与索引字段不同,虽然MySQL能够进行数据类型转换,但却不会使用索引,从而导致InnoDB使用表锁。通过用explain检查两条SQL的执行计划,我们可以清楚地看到了这一点:

mysql> explain select * from jjj where a=30;   #没有索引,rows是全部行;

mysql> explain select * from jjj where id=30;    #有索引,rows是仅一行!

三、间隙锁(Next-Key锁):

1)当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,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(这些记录并不存在)的“间隙”加锁。

还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁!比如上面的查询中,如修改为Select * from  emp where empid > 105 for update;(105是不存在的),那么当你插入empid为大于105如200的记录是不允许的。

2)InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使 用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需 要。

3)在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。

四、事务的隔离性:

事务的隔离性(真正的隔离性):一个事务所做的修改对另一个事务来说是不可见的,就好像是串行执行;官方测试,RR比RC的性能要好。

Innodb没有库级别与页级别的锁;

1、脏读的概念测试:

Read uncommitted \ Read committed   \Repeatable read \  serializable  (RR是真正符合隔离要求的)

Truncate table b;

Set tx_isolation=’ READ-UNCOMMITTED’;

use test; insert into b(2,2);            #第一个窗口

select * from b;              

#打开另一个b窗口,会发现能查到没有提交的数据(没有提交的是脏数据),破坏了隔离性的要求;

2、不可重复读测试(RC级别时,b一个窗口提交了一个update更新,执行了begin的a窗口本来是停留在b窗口提交前的状态,第二次查询时却发现数据变了,破坏了事务隔离性的要求):

mysql>  set tx_isolation='REPEATABLE-READ';           ##a\b窗口都要执行,这里测试RR级别;

Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> flush privileges;                              ##不执行不生效。

Query OK, 0 rows affected (0.01 sec)

mysql> show variables like '%iso%';

+-----------------------+-----------------+

| Variable_name         | Value           |

+-----------------------+-----------------+

| transaction_isolation | REPEATABLE-READ |

| tx_isolation          | REPEATABLE-READ |

+-----------------------+-----------------+

2 rows in set (0.00 sec)

mysql>  update jjj set a=42 where id=15;     a窗口执行,RR级别不影响修改数据;

mysql>  insert into jjj value(21,21);         a窗口执行,RR级别不影响修改数据;

mysql> commit;                          a窗口执行提交;

mysql> select * from jjj where id=15;        #b窗口,会发现数据没有变化(现实了可重复读)

mysql> select * from jjj;                   #b窗口,会发现没有id为21的数据(避免了幻读)。

此时,会发现给jjj的表加了意向排它锁IX和record lock:

 

##最高级别的serializable测试(serializable最严格,如果a窗口use bstest;begin;  那么b窗口当insert\update数据时,直接锁住):

mysql> show variables like '%iso%';

+-----------------------+--------------+

| Variable_name         | Value        |

+-----------------------+--------------+

| transaction_isolation | SERIALIZABLE |

| tx_isolation          | SERIALIZABLE |

+-----------------------+--------------+

2 rows in set (0.00 sec)

mysql>   insert into jjj value(22,22);

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql>  update jjj set a=35 where id=15; 

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

3、幻读:即在一个事务中读到了之前查询时不存在的数据(如有insert并提交的数据,在RC、RR中不能避免幻读);

 五、行锁:

1)行锁的三个模式:1.Record Lock :锁定的是30这个记录本身;

2.Gap Lock :锁定一个范围,如在查询范围的10-30(不包括30本身),即禁止插入10-30之间的记录,如20,这样就解决了幻读问题;

3.Next-key Lock :  Gap Lock + Record Lock,即锁定一个范围,且锁定记录本身,如下例:

 mysql>  begin;

mysql> update bstest.jjj set a=2222 where id>22;

 这里的多条Record已属于next-key lock

无论是RR还是Rc的隔离级别的update或insert,只要用到了索引,只会根据索引进行加锁,没有用到索引则会对所有的记录加锁,因为是全表查找(RR的隔离级别才会,RC则不影响其它的修改)

会话1(a列没有加锁):

mysql> show variables like '%iso%';

+-----------------------+-----------------+

| Variable_name         | Value           |

+-----------------------+-----------------+

| transaction_isolation | REPEATABLE-READ |

| tx_isolation          | REPEATABLE-READ |

+-----------------------+-----------------+

2 rows in set (0.00 sec)

mysql> update bstest.jjj set a=383 where a=38888;

会话2(a列没有加锁)::

mysql>  show variables like '%iso%';

+-----------------------+-----------------+

| Variable_name         | Value           |

+-----------------------+-----------------+

| transaction_isolation | REPEATABLE-READ |

| tx_isolation          | REPEATABLE-READ |

+-----------------------+-----------------+

2 rows in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql>  update bstest.jjj set a=15 where a=40;  

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

会话1(修改方是Rc,则没有影响):

mysql>   show variables like '%iso%';

+-----------------------+----------------+

| Variable_name         | Value          |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

| tx_isolation          | READ-COMMITTED |

+-----------------------+----------------+

2 rows in set (0.00 sec)

mysql> begin;

Query OK, 0 rows affected (0.00 sec)

mysql> update bstest.jjj set a=222 where a=2;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0

会话2(修改方是Rc,则没有影响):

mysql> begin;

mysql>  update bstest.jjj set a=15 where a=40;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0

mysql>  show variables like '%iso%';

+-----------------------+----------------+

| Variable_name         | Value          |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

| tx_isolation          | READ-COMMITTED |

+-----------------------+----------------+

Xbackup存在问题:细节上(且备份后的数据量与原文件大小一样)。还是官方的mysqldump比较可靠,且备份的数据量小(逻辑备份)。

 六、关于自增列有关的锁:

事物回滚后,自增值不会跟着回滚,导致自增值不连续,但是这个值连续也没什么意义、

##自增有关的参数:

auto_increment_increment = 1

auto_increment_offset = 1

1)如果插入前能确定行数的,就是simple inserts(在SQL运行完之前, 确定了自增值之后,就可以释放自增锁了)

insert into table_1 values(NULL, 1), (NULL, 2);

2)如果插入前不能确定行数的,就是bulk inserts (在SQL执行完之后,AI锁才释放)

insert into table_1 select * from table_2;

innodb_autoinc_lock_mode={0|1|2}

(innodb_autoinc_lock_mode 是read-only 的, 需要修改后重启MySQL实例)

0 传统方式

在SQL语句执行完之后,AI锁才释放

例如:当insert ... select ... 数据量很大时(比如执行10分钟),那在这个SQL执行完毕前,其他事物是不能插入的(AI锁未释放)

这样可以保证在这个SQL语句内插入的数据,自增值是连续的,因为在这个10分钟内,AI自增锁是被这个SQL持有的,且没有释放

1 默认参数( 大部分情况设置为1 )

◾ bulk inserts, 同传统方式一样,对于bulk inserts 的方式,和0 - 传统方式一样,在SQL执行完之后,AI锁才释放

◾ simple inserts, 并发方式,在SQL运行完之前, 确定了自增值之后,就可以释放自增锁了

因为bulk inserts 不知道要插入多少行,所以只能等insert结束后,才知道N 的值,然后一次性(ai + N)

而simple inserts 知道插入的行数(M),所以可以先(ai + M) ,然后将锁释放掉,给别的事物用,然后自己慢慢插入数据

2

◾ 所有自增都可以并发方式( 不同于Simple inserts的方式)

◾ 同一SQL语句自增可能不连续

猜你喜欢

转载自www.cnblogs.com/liulvzhong/p/11988460.html