一、事务的隔离级别/锁问题
基本的介绍: 当我们的mysql表,被多个线程或者客户端同时操作时,mysql提供一种机制,可以让不同的事务在操作数据时,具有隔离性。
锁是计算机协调多个进程或线程并发访问某一资源的机制。锁冲突也是影响数据库并发访问性能的一个重要因素。MySQL不同的存储引擎支持不同的锁机制,如 MyISAM 和 MEMORY 存储引擎采用表级锁,BDB 采用页面锁,也支持表级锁;InnoDB 既支持行级锁,也支持表级锁,默认采用行级锁;
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
页面锁:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般;
锁冲突:例如说事务A将某几行上锁后,事务B又对其上锁,锁不能共存否则会出现锁冲突。(但是共享锁可以共存,共享锁和排它锁不能共存,排它锁和排他锁也不可以)
死锁:例如说两个事务,事务A锁住了1~5行,同时事务B锁住了6~10行,此时事务A请求锁住6~10行,就会阻塞直到事务B施放6~10行的锁,而随后事务B又请求锁住1~5行,事务B也阻塞直到事务A释放1~5行的锁。死锁发生时,会产生Deadlock错误。两个事务都需要获得对方持有的排他锁才能继续完成事务,形成循环锁成死锁。如果锁是对表操作的,所以自然锁住全表的表锁就不会出现死锁。
Show Innodb status 命令查看最后一个死锁的产生原因。
从锁的角度看,表级锁更适合用于以查询为主,只有少量按索引条件更新数据的应用;而行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用。
二、MyISAM 表锁
查询表级锁征用情况:
Show status like ‘table%’;
如果 table_locks_waited 值较高,则说明存在较严重的表级锁征用情况;
对于MyISAM表
读锁,表共享读锁,不会阻塞其他用户对同一表的读请求,但会阻塞写请求(即我读的时候我不能改,你能读也不能写【写等待】);
直到解锁表才执行:
写锁,表独占写锁,则即阻塞读也阻塞写(我能读写,你不能读且不能写【读写等待】);
读写互斥(别人在读的时候你不能写,在写的时候不能读),但在一定条件下,也支持查询和插入操作的并发进行,设置系统变量concurrent_insert 值为0,1,2
MyISAM 在执行 SELECT 前,自动 给涉及的所有表加 读锁,执行 UPDATE、DELETE|、INSERT 等前,自动加写锁;一般不需要手动显示加锁。
MyISAM 总是一次获得SQL语句所需的全部锁,所以不会出现死锁;
在执行 LOCK TABLES 后,只能访问显示加锁的这些表,不能访问未加锁的表,自动加锁的情况也是如此;
如果SQL中出现锁定表取表名的,也需要显示申明锁定别名表。
Lock tables a read, b read ; # 同时锁定 a, b表
给MyISAM表显式加锁,一般是为了在一定程度模拟事务操作,实现对某一时间点多个表的一致性读取。例如有一个订单表,其中记录有各订单的总金额total,同时还有一个订单明细表 order_detail, 其中记录有各个订单每一产品的金额小计 subtotal, 假设我么要检查这两个表的金额合计是否相符,就需要给两个表加 读锁 ,防止在第一个表统计的过程中, order_detail 表已经发生了改变。
MyISAM的锁调度:
MyISAM的读锁和写锁互斥,读写操作是串行的。那么,一个进程请求读锁,同时另一个进程请求同一表的写锁,写进程优先获得锁,即使读请求先到锁等待队列,写请求后到,写锁也会插队到读锁请求之前。这也是MyISAM不适合有大量更新操作和查询操作应用的原因。因为有大量的更新操作会造成查询操作很难获得读锁,从而可能永久阻塞。尽量避免长时间运行的查询操作,如复杂查询不可避免,尽量安排在数据库空闲时段执行,如夜间
解决查询相对重要的应用中读锁等待严重的问题:
1.指定启动参数low_priority_updates,使MyISAM引擎给予读请求以优先的权利
2.命令set low_priority_updates = 1,使该连接发出的更新请求优先级降低
3.指定insert、update、delete语句的low_priority属性,降低该语句的优先级
4.折中,系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会;
并发插入(系统变量concurrent_insert):
concurrent_insert = 0 ,不允许并发插入
concurrent_insert = 1,如果MyISAM表中没有空洞(没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。Mysql的默认设。比如,线程session1执行 lock table test read local 语句后就不能对该表写操作(innodb的不行,还是写等待)。但另一个线程session2虽然不能更新和删除,但却可以在尾部并发插入操作,且插入的数据在session1没有释放锁时是无法查询到的。
concurrent_insert =2,无论有没有空洞,都允许在表尾并发插入记录
利用MyISAM的并发插入来解决应用中对同一表查询和插入的锁争用。通过定期在系统空闲时执行 optimize table 语句来整理空间碎片,回收因删除而产生的中间空洞。
三、查看Innodb行锁争用情况
Show status like ‘innodb_row_lock%’;
Innodb_row_waits 和 Innodb_row_lock_time_avg 的值比较高,可
查看 information_schema 数据库中相关的表,如 innodb_locks 和 innodb_lock_waits;
或
通过设置 Innodb monitors 来观察锁冲突情况:
Create table innodb_monitor(a int) engine=innodb;
然后 Show engine innodb statusG 查看
Drop table innodb_monitor; //停止监视器,默认情况下每15秒写入监控日志
四、Innodb实现两种类型的行锁(针对开启事务,否则无效):
innodb 表锁操作跟 myisam 一样。与MyISAM最大的不同点有两点,一是支持事务,二是采用了行级锁。
共享锁(S):读锁。当一个事务(必要条件否则无效)对某几行上读锁时,我可以读写,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
上共享锁的写法:select 语句 … lock in share mode
得出结论:允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。注意:如果两个事务都对同一条数据上了共享锁,然后session1 要更新这条数据(会写等待),若此时session2 也对这条数据进行更新(则会造成死锁导致退出),session1的写执行。
排他锁(X):写锁。当一个事务对某几个上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。包括写锁。
上排它锁的写法:select 语句 … for update
如果一个事务请求的锁模式与当前线程的锁兼容,Innodb 就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
即能同时获得共享锁,但不能同时获得排他锁,会锁等待
对于 UPDATE、DELETE、INSERT 语句,InnoDB会自动给涉及数据集加排他锁X;对于普通的 SELECT 语句,InnoDB不会加任何锁。意向锁是系统自动加的。
得出结论:排他锁,允许获的排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁
注意几点:
- Innodb行锁是通过给索引上的索引项加锁来实现。如果没有索引将通过隐藏的聚簇索引来对记录加锁,即如果不通过索引条件检索数据,将对表中所有记录加锁,实际效果跟表锁一样。
- 两个事务不能锁同一个索引
- insert ,delete , update在事务中都会自动默认加上排它锁。
- 在没有索引的情况下,Innodb会对所有记录都加锁,当给其增加一个索引后,Innodb只锁定了符合条件的行。
- 访问不同行的记录,但如果使用相同索引键,会出现锁冲突