扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
MySQL 5.1支持对MyISAM和MEMORY表进行表级锁定,对BDB表进行页级锁定,对InnoDB表进行行级锁定。
创新互联公司成立与2013年,公司以网站建设、网站制作、系统开发、网络推广、文化传媒、企业宣传、平面广告设计等为主要业务,适用行业近百种。服务企业客户上1000+,涉及国内多个省份客户。拥有多年网站建设开发经验。为企业提供专业的网站建设、创意设计、宣传推广等服务。 通过专业的设计、独特的风格,为不同客户提供各种风格的特色服务。
如果不能同时插入,为了在一个表中进行多次INSERT和SELECT操作,可以在临时表中插入行并且立即用临时表中的记录更新真正的表。
这可用下列代码做到:
mysql LOCK TABLES real_table WRITE, insert_table WRITE;
mysql INSERT INTO real_table SELECT * FROM insert_table;
mysql TRUNCATE TABLE insert_table;
mysql UNLOCK TABLES;
1、在mysql数据库中如何锁定一行数据,保证不被其他的操作影响。
2、从对数据的操作类型分为读锁和写锁。从对数据操作的粒度来分:表锁和行锁。
3、现在我们建立一个表来演示数据库的行锁讲解。
4、行锁基本演示如下图所示。
5、如果两个会话操作的是不同的行,就不会互相阻塞了。
mysql中使用select
for
update的必须针对InnoDb,并且是在一个事务中,才能起作用。
select的条件不一样,采用的是行级锁还是表级锁也不一样。
由于
InnoDB
预设是
Row-Level
Lock,所以只有「明确」的指定主键,MySQL
才会执行
Row
lock
(只锁住被选取的资料例)
,否则
MySQL
将会执行
Table
Lock
(将整个资料表单给锁住)。
举个例子:
假设有个表单
products
,里面有
id
跟
name
二个栏位,id
是主键。
例1:
(明确指定主键,并且有此笔资料,row
lock)
SELECT
*
FROM
products
WHERE
id='3'
FOR
UPDATE;
例2:
(明确指定主键,若查无此笔资料,无
lock)
SELECT
*
FROM
products
WHERE
id='-1'
FOR
UPDATE;
例2:
(无主键,table
lock)
SELECT
*
FROM
products
WHERE
name='Mouse'
FOR
UPDATE;
例3:
(主键不明确,table
lock)
SELECT
*
FROM
products
WHERE
id'3'
FOR
UPDATE;
例4:
(主键不明确,table
lock)
SELECT
*
FROM
products
WHERE
id
LIKE
'3'
FOR
UPDATE;
注1:
FOR
UPDATE
仅适用于
InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效。
注2:
要测试锁定的状况,可以利用
MySQL
的
Command
Mode
,开二个视窗来做测试。
下面通过一个例子来说明
场景如下:
用户账户有余额,当发生交易时,需要实时更新余额。这里如果发生并发问题,那么会造成用户余额和实际交易的不一致,这对公司和客户来说都是很危险的。
那么如何避免:
网上查了下,有以下两种方法:
1、使用悲观锁
当需要变更余额时,通过代码在事务中对当前需要更新的记录设置for update行锁,然后开始正常的查询和更新操作
这样,其他的事务只能等待该事务完成后方可操作
当然要特别注意,如果使用了Spring的事务注解,需要配置一下:
!-- (事务管理)transaction manager, use JtaTransactionManager for global tx --
bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
property name="dataSource" ref="dataSource" /
/bean
!-- 使用annotation定义事务 --
tx:annotation-driven transaction-manager="transactionManager" /
在指定代码处添加事务注解
@Transactional
@Override
public boolean increaseBalanceByLock(Long userId, BigDecimal amount)
throws ValidateException {
long time = System.currentTimeMillis();
//获取对记录的锁定
UserBalance balance = userBalanceDao.getLock(userId);
LOGGER.info("[lock] start. time: {}", time);
if (null == balance) {
throw new ValidateException(
ValidateErrorCode.ERRORCODE_BALANCE_NOTEXIST,
"user balance is not exist");
}
boolean result = userBalanceDao.increaseBalanceByLock(balance, amount);
long timeEnd = System.currentTimeMillis();
LOGGER.info("[lock] end. time: {}", timeEnd);
return result;
}
MyBatis中的锁定方式,实际测试该方法确实可以有效控制,不过在大并发量的情况下,可能会有性能问题吧
select id="getLock" resultMap="BaseResultMap" parameterType="java.lang.Long"
![CDATA[
select * from user_balance where id=#{id,jdbcType=BIGINT} for update;
]]
/select
2、使用乐观锁
这个方法也同样可以解决场景中描述的问题(我认为比较适合并不频繁的操作):
设计表的时候增加一个version(版本控制字段),每次需要更新余额的时候,先获取对象,update的时候根据version和id为条件去更新,如果更新回来的数量为0,说明version已经变更
需要重复一次更新操作,如下:sql脚本
update user_balance set Balance = #{balance,jdbcType=DECIMAL},Version = Version+1 where Id = #{id,jdbcType=BIGINT} and Version = #{version,jdbcType=BIGINT}
这是一种不使用数据库锁的方法,解决方式也很巧妙。当然,在大量并发的情况下,一次扣款需要重复多次的操作才能成功,还是有不足之处的。不知道还有没有更好的方法。
手动加锁:
表级锁
lock tables tablename read;//共享锁
lock tables tablename write;//排它锁
unlock tables;//解锁
php直接query就行了。
除了MyIsam,谁还会无聊到显式加锁?
InnoDB直接跑事务默认会触发隐式锁,不需要自己lock和unlock。
SELECT ... LOCK IN SHARE MODE sets a shared mode lock on the rows read
你可以这样写SQL
SELECT * FROM table_name LOCK IN SHARE MODE
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流