注意:所有文章除特别说明外,转载请注明出处.
锁问题 - InnoDB锁问题
[TOC]
锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。
表级锁:开销小,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。
表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用。
行级锁:开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度高。
行级锁更适用于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用。
页面锁:开销和加锁时间介于表锁和行锁之间,会出现死锁,锁定粒度介于表锁和行锁之间,并发度一般。
查询表级锁争用的情况
show status like 'table%'
- 查看table_locks_waited和table_locks_immediate状态变量分析系统上的表锁定争夺。
表级锁的锁模式
表级锁的两种模式,表共享模式和表独占写锁。
加入表锁
lock table 显式给某一表加上表锁。
获取InnoDB行锁争用情况
show status like 'innodb_row_lock%'
- innodb_row_lock_waits 和 innodb_row_lock_time_avg值比较高,可以通过查询information_schema数据库中相关的表来查看锁的情况
InnoDB行锁模式以及加锁方法
1. 共享锁(S),允许一个事务去读一行,阻止其它事务获得相同数据集的排他锁。
2. 排它锁(X),允许获得排它锁的事务更新数据,阻止其它事务取得相同数据集的共享锁和排它锁。
提示:为了允许行锁和表锁共存,实现多粒度锁机制,innodb还有两种内部使用的意向锁(表锁)。
InnoDB行锁实现方式
InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来对记录加锁。
注意:该行锁实现特点意味着,如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。
1. 在不通过索引条件查询时,InnoDB会锁定表中的所有记录。
2. 由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然访问不同行的记录,但是如果是使用相同的索引键,会出现锁冲突的。
3. 不论使用何种所以,InnoDB都会使用行锁来对数据加锁。
4.
事务概念
事务是一组操作的执行单元,相对于数据库操作来说,事务管理的是一组SQL指令(增删改),事务的一致性要求这个事务内的操作必须全部执行成功,如果在此过程出现差错,如一条sql语句没有执行成功,那么这一组操作都将全部回滚。
1.事务的性质(ACID)
这里可以用四个词来解释事务:
A(atomic 原子性):发生就是全部都发生,不发生全都不发生
C(consistent 一致性):数据应该不被破坏
I(isolate 隔离性):用户间操作不相混淆
D(durable 持久性):永久保存,如保存到数据库中等
2.事务管理接口
所谓的事务管理就是按照给定的事务规则来执行提交和回滚事务操作。
0.0 spring事务管理的两种方式
1)编程式事务管理(不用)
2)声明式事务管理
1.基于xml配置文件实现
2.基于注解方式实现
0.1 声明式事务管理(xml配置)
1.配置文件的方式使用aop思想配置
1.配置事务管理器
<bean id="" class=""></bean>
2.配置事务的增强
<tx:advice id="txadvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="" propagation=""/>//指定运行事务的匹配规则 propagation:
</tx:attributes>
</tx:advice>
3.配置切面
<aop:config>
//1.切入点
<aop:pointcut expression="" id=""/>
//2.切面
<aop:advisor advice-ref="" pointcut-ref=""/>//表示的意思是将哪个增强(advice-ref)用到哪一个切面(pointcut-ref)上面
</aop:config>
2.
0.2 声明式事务管理(注解)
1.配置事务管理器
<bean id="transactionManager" class="org.springframwork.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2.配置事务注解
<tx:annnotation-driven transaction-manager="transactionManager"/>
3.在要使用事务的方法所在类上面添加注释
@Transactional
public class OrdersService{
private OrdersDao ordersDao;
public void setOrdersDao(OrdersDao ordersDao){
}
//调用dao的方法,业务逻辑层写转账业务等
//要使用到的业务逻辑
public void accountMoney(){
}
}
1.PlatformTransactionManager 接口1
1.事务管理器:PlatformTransactionManager
1)Spring针对不同的DAO层框架都提供了不同的实现类
2)无论是使用基于xml配置文件方式实现事务管理还是使用基于注解方式实现事务管理,首先都需要配置事务管理器
案例:搭建转账环境
1.创建数据库表,添加数据(创建基本的用户信息(用户名、密码))
2.创建service类和dao类,完成注入关系
<bean id="orderService" class="cn.itcast.service.OrderService">
<property name="ordersDao" ref="ordersDao"></property>
</bean>
<bean id="ordersDao" class="cn.itcast.dao.OrdersDao">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
注意:service层又叫做业务逻辑层,dao层单纯对数据库做操作,在dao层不添加业务
注意:在bean.xml文件配置时候是service中注入dao,dao中注入模板,模板中注入dataSource
2.TransactionDefinition 接口2
事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
3.TransactionStatus 接口3
事务运行状态
4.事务隔离级别
事务的隔离级别定义一个事务可能受其他并发事务影响的程度。在我们项目中,多个事务并发运行会操作相同的数据来完成各自的任务,所以在这一过程中会导致一些问题的出现。
1.脏读
脏读表示一个事务正在访问数据并对数据进行修改,而这修改还没有提交到数据库中,另外的事务也访问该数据,然后使用这个数据,因为这个数据还没有提交,所以另外一个事务读到的数据是脏数据,所做的操作是不正确的。
2.丢失修改
表示在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改这个数据后,第二个事务也修改这个数据,这样第一个事务内修改的数据结果丢失,从而称之为丢失修改。
3.不可重复读
表示在一个事务内多次读同一数据,在这个事务还没有结束时,另一个事务也访问该数据。那么在两个事务操作之后,第一个事务读取的数据可能不一样。
4.幻读
幻读与不可重复读类似,表示一个事务读取数据,然后另外一个并发事务又插入数据,从而在第一个事务读取时会发现一些原本不存在的数据,这就像幻觉,从而是幻读。
注意:为了解决数据库读一致性的问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式:1.在读取数据前对其加锁,阻止其它事务对其进行修改。2.通过一定机制生成一个数据请求时间点的一致性数据快照,并用这个快照来提供一定级别的一致性读取。
5.获取InnoDB行锁争用情况
在这里通过innodb_row_lock状态命令来分析系统上的行锁争用情况。
SHOW STATUS LIKE 'innodb_row_lock%'
5.1 InnoDB的行锁模式和加锁方法
InnoDB实现两种类型行锁:1.共享锁,允许一个事务去读一行,阻止其它事务获得相同数据集的排它锁。2.排它锁,允许获得排它锁的事务更新数据,阻止其它事务取得相同数据集的共享读锁和排它写锁。
5.2 InnoDB行锁实现方式
InnoDB行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来记录加锁。
1.Record lock:对索引项加锁
2.Gap lock:对索引之间的间隙加锁,第一条记录前或者最后一条记录之后的间隙加锁
3.Next-key lock:前两种的组合,对记录及其前面的间隙加锁
提示:InnoDB这种行锁实现的特点意味着,如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。
注意:在实际应用过程中需要注意InnoDB行锁的这一特性,否则将导致大量的锁冲突。
1.在不通过索引条件查询时,InnoDB会锁定表中的所有记录
2.因为MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现冲突的。
3.当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,不管是使用主键索引、唯一索引还是普通索引,InnoDB都会使用行锁来对数据加锁
4.有些时候,即使在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全盘扫描效率高,其就不会使用索引,这时InnoDB会对所有记录加锁。
5.3 什么时候使用表锁
对于InnoDB表,绝大部分时间都使用行级锁,因为事务和行级锁往往使我们选择InnoDB的理由。
1.如果事务需要更新大部分或者全部数据,表比较大,这种情况可以使用表锁来提高效率。
2.事务涉及多个表,复杂,容易引起死锁。这种情况使用表锁。
5.4 避免死锁
1.如果不同程序并发存取多个表,尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
2.程序批量处理数据的时候,如果事先对数据排序,保证每个线程按照固定的顺序来处理记录,也可以降低出现死锁的机会。