| 故事背景

  话说有一回,X市X公司的产品经理Douni兴致冲冲的跑来和Sum(Sum,X市X公司资历8年程序猿,技能:深思、熟虑、心细、深究、技术过敏。口头禅:嗯,容我想想。坚信:只要赚钱的业务,我都可以让一枚程序猿完成,如果不行,那就再加一枚。)说:“最近公司的B2C业务不景气,需要开发代理功能,我们产品部正在开产品研讨会,要不要一起来参加。”,Sum摸了一把下巴,嘴角露出一丝诡异的笑容,目光沿着45度角向产品经理投射过去,说:“容我,想想。”。话音未落,已被Douni抬离卡座,电光火石间仿佛听到Doubi那久久未能散去的余音:“这事没你不行!!!”

| 需求分析

  Sum在产品研讨会上讨论(激烈争吵)之后,最终确定了需求。

  需求定义是在X公司的一个B2C电商平台增加一个代理功能,让指定的老用户升级为代理,从而推动该电商的流水,代理也可以从中获取到可观的报酬。

  这个需求对于资深、心细的Sum来说,并未有挑战。但是Sum虽然自称是猿,但毕竟来说,还是一个小团队的头儿。所以,在经过整理需求之后,发现了一个比较容易犯错,且若交给队员去实现的话,有延期风险的技术难点。所以,Sum决心自己先过一遍核心思路,再把该功能交给队友去做。

| 开干

  该功能点对于Sum来说,并不难。主要是在该平台高并发下,代理利润分成的结算。这设计到了数据的一致性和可靠性。在有限的开发条件下,Sum的第一念头是写一个存储过程,在存储过程当中,使用事务对数据进行修改。于是,Sum在分配完其余工作之后,开始着手编写这个存储过程。

  Sum打开phpstudy,开启mysql(项目用的是PHP、mysql的环境),然后打开SQLyog,进入本地的环境。

  很熟练的,新建了一个数据库demo。

  接着建立用户表names,注意,该表的存储引擎是InnoDB(InnoDB具有事务回滚、提交、原子性等功能)

 CREATE TABLE `names` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4

  再建立一张资金表testa:

 CREATE TABLE `testa` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(40) DEFAULT NULL,
`money` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4

  万事已具备,只欠编写代码了。Sum坐在电脑前,深思了一会。突然间,双手放在键盘上。。。

  经过了大概十几分钟,显示器出现了一连串代码:

  

 DELIMITER $$

 USE `demo`$$

 DROP PROCEDURE IF EXISTS `test`$$

 CREATE DEFINER=`root`@`localhost` PROCEDURE `test`(IN id INT(11) UNSIGNED, IN m DECIMAL(10,2) UNSIGNED)
test:BEGIN
DECLARE t_error INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1; SELECT `name` INTO @t_name FROM `names` WHERE `id`= id;-- 查出用户的信息
SET AUTOCOMMIT = 0;-- 关闭自动提交SQL语句
START TRANSACTION;-- 开启事务,当然开启事务还有BEGIN
SELECT `money` INTO @money FROM `testa` WHERE `name`=@t_name FOR UPDATE;-- 查询当前余额,这一步为什么查询出来而不是直接锁?大家思考下。
UPDATE `testa` SET `money`=@money+m WHERE `name`=@t_name;-- 更新资金库 IF t_error = 1 THEN-- 失败则回滚
ROLLBACK;
ELSE
COMMIT;-- 成功则提交
END IF; SELECT t_error;-- 返回该事务的处理代码0|1
END$$ DELIMITER ;

  Sum摸了一把下巴,满意的喝了一口咖啡,Ctrl+F9,这个存储过程就创建好了。

  接下来就是调试阶段,任何程序猿对外都会说,我的代码是没有BUG的,即使心中对自己有一万个为什么。对于自认为骨子里有架构师天分的Sum来说,在未对外吹牛逼的时候,自测是非常重要的环节。于是他兴奋的打开了一个查询窗口,潇潇洒洒的写上了一句:

  call ·test·(1,33.00);

  这段代码他心中已经运行了好几遍,也检查了好几遍,自认为并未有传说中程序杀手--BUG--的存在,充满自信的按下了Ctrl+F9。

  万万没想到,运行结果竟然是!!!!!

  

  非常刺激!!!Sum手一抖,咖啡差些洒在办公桌上!!昂无里窝波儿!!简直不敢相信!!Sum把咖啡往桌上一放,目光立即盯着该段存储过程的代码,一行行往下扫去!!!

  “没问题啊没问题啊,问题出在哪里啊!!!”脑中蹦出无数个没问题,Sum此时都快疯了。心想,“幸好,不把这个工作丢给队员,这操作简直太正确了!!!”

  在思考无果后,Sum各种百度谷歌。。。。。(省去一小时寂静无语的时光)

  终于在网上找到了一个解决方案

  

 -- 如果出现执行异常则结束后继的执行,并执行begin-end中的处理
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
-- 回滚事务
ROLLBACK;
-- 获取错误信息
GET DIAGNOSTICS CONDITION 1 @p1=RETURNED_SQLSTATE,@p2= MESSAGE_TEXT;
-- 借“模拟”抛出异常
RESIGNAL SET schema_name = 'mtt_dev',
table_name = 'tb_test',
message_text = @p2,
mysql_errno = @p1;
END;

当Sum把代码小心翼翼的合并到存储过程中后,按下Ctrl+F9,竟然报错了

  

  错误代码: 1064
  You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DIAGNOSTICS CONDITION 1 @p1=RETURNED_SQLSTATE,@p2= MESSAGE_TEXT;
  -- 借“' at line 10

  原来,这个解决方案是在mysql5.6版本或以上版本才能使用,而Sum本地的环境是5.1版本的mysql,所以提示了报错。MySQL 5.6 提供了 get diagnostic 语句来获取错误缓冲区的内容

  这一方案在电光火石间就被抛弃了。Sum又陷入了新一轮的惆怅当中,可是有着架构师天赋的Sum岂是说放弃就放弃的?于是,他开始了新一轮的调试。

  Sum每一行每一行的进行调试,也不忘在X技术群里问老前辈们。

  终于有发现!!!(已经过了3个小时)

  Sum发现,在第一句“SELECT `name` INTO @t_name FROM `names` WHERE `id`= id LIMIT 1”加上LIMIT 1,竟然就过了!!!

  如法炮制,Sum把剩余的有关查询和更新的语句都加上LIMIT 1,然后按下Ctrl+F9,再次拿起咖啡,点击运行call ·test·(1,33.00);

  完美运行!!!

  Sum这可高兴坏了,一看时间,已经是17:00了,于是放下咖啡,快速的编写技术方案,做好demo,交付给队员!

| 最终代码

 DELIMITER $$

 USE `demo`$$

 DROP PROCEDURE IF EXISTS `test`$$

 CREATE DEFINER=`root`@`localhost` PROCEDURE `test`(IN id INT(11) UNSIGNED, IN m DECIMAL(10,2) UNSIGNED)
test:BEGIN
DECLARE t_error INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET t_error = 1; SELECT `name` INTO @t_name FROM `names` WHERE `id`= id LIMIT 1;-- 查出用户的信息
SET AUTOCOMMIT = 0;-- 关闭自动提交SQL语句
START TRANSACTION;-- 开启事务,当然开启事务还有BEGIN
SELECT `money` INTO @money FROM `testa` WHERE `name`=@t_name LIMIT 1 FOR UPDATE;-- 查询当前余额,这一步为什么查询出来而不是直接锁?大家思考下。
UPDATE `testa` SET `money`=@money+m WHERE `name`=@t_name LIMIT 1;-- 更新资金库 IF t_error = 1 THEN-- 失败则回滚
ROLLBACK;
ELSE
COMMIT;-- 成功则提交
END IF; SELECT t_error,@t_name;-- 返回该事务的处理代码0|1
END$$ DELIMITER ;

| 总结

  1.当在事务中select 【字段】 into @用户变量 的时候,请确保结果集是一条,如果是多条,请使用游标!

  2.使用for update的时候,一定要命中索引字段,不然会锁表,违背了高并发时候,处理事务的初衷

  3.小的表,没有索引,尽量的就不用for update,因为mysql的执行计划

  4.for update 叫做悲观锁,命中索引的时候,叫做行锁,不命中的时候,叫做表锁,如果结果集为空,则无锁!

  

资深架构师Sum的故事:(Mysql)InnoDB下,存储过程中事务的处理的更多相关文章

  1. 资深架构师Sum的故事:正则!入门就是这样简单

    | 故事背景 职场如战场!Sum带领三个小队友用了两周,成功把代理功能给干出来了.如果说产品经理是最魔鬼的指挥官,那测试就是最魔鬼的教官.这两周,让Sum深深领略了什么是X市的日出. 不过话又说回来, ...

  2. MySQL InnoDB存储引擎中的锁机制

    1.隔离级别 Read Uncommited(RU):这种隔离级别下,事务间完全不隔离,会产生脏读,可以读取未提交的记录,实际情况下不会使用. Read Committed (RC):仅能读取到已提交 ...

  3. Mysql InnoDB下的两种行锁

    今天例举2种常见的Mysql InnoDB下的行锁 现有表dr_test(id pk, name) 数据是 1 zhangsan2 lisi3 wangwu 例子1 事务1 update dr_tes ...

  4. mysql 视图 触发器 存储过程 函数事务 索引

    mysql 视图 触发器 存储过程 函数事务 索引 视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,并可以将其当 ...

  5. 一名资深架构师规划Java程序员五年职业生涯指南

    每个程序员.或者说每个工作者都应该有自己的职业规划,如果你不是富二代,不是官二代,也没有职业规划,希望你可以思考一下自己的将来.今天我给大家分享的是一篇来自阿里大牛对五年工作经验程序员的职业建议,希望 ...

  6. MySQL InnoDB下关于MVCC的一个问题的分析

      这个是网友++C++在群里问的一个关于MySQL的问题,本篇文章实验测试环境为MySQL 5.6.20,事务隔离级别为REPEATABLE-READ ,在演示问题前,我们先准备测试环境.准备一个测 ...

  7. mysql(函数,存储过程,事务,索引)

    函数 MySQL中提供了许多内置函数: 内置函数 一.数学函数 ROUND(x,y) 返回参数x的四舍五入的有y位小数的值 RAND() 返回0到1内的随机值,可以通过提供一个参数(种子)使RAND( ...

  8. 【架构】生成全局唯一ID的3个思路,来自一个资深架构师的总结

    标识(ID / Identifier)是无处不在的,生成标识的主体是人,那么它就是一个命名过程,如果是计算机,那么它就是一个生成过程.如何保证分布式系统下,并行生成标识的唯一与标识的命名空间有着密不可 ...

  9. 【大数据系统架构师】0.3 MySQL数据库

    1. MySQL的基本操作 2. SQL语句 3. 高级查询 1)聚合函数.分组查询 2)联合查询.连接查询 3)子查询 4. 高级应用 1)视图与索引 2)数据可视化管理 5. 使用JDBC操作数据 ...

随机推荐

  1. php 学习编译扩展

    原文 : http://kimi.it/496.html 系统环境 : Ubuntu 目标 : 可以像 php 提供的内部函数一样,使用 myecho 函数 : 输出如下 : 1. 获取 php 的源 ...

  2. ‎Cocos2d-x 学习笔记(23) 分辨率与屏幕适配

    Cocos2d-x的分辨率可以分为两种:屏幕分辨率和设计分辨率. 屏幕分辨率就是屏幕窗口的大小,单位是像素. 设计分辨率单位是点,一个点可能包括多个像素. 如果把一台显示器自身的分辨率比作屏幕分辨率的 ...

  3. 网页背景H5视频自动播放---PC端、移动端兼容问题完美解决方案(IOS、安卓、微信端)

    最近公司官网需要使用视频当做banner背景且自动播放,并且因为是官网需要做到PC端和移动端都可以适配兼容,这些问题很是头疼: 兵来将挡,水来土掩,进过查阅相关技术资料,现已完美兼容PC端和移动端.下 ...

  4. C++ Web框架::cintara

    1.Cinatra是由C++开源社区purecpp发起的一个开源项目,是一个现代C++写的Web框架,旨在给用户提供一个易用.灵活和高性能的Web框架,让用户能完全专注于核心逻辑而无需关注http细节 ...

  5. 从零开始把项目发布到Nuget仓库中心

    从零开始把项目发布到Nuget仓库中心 我的项目地址 https://github.com/Ants-double/dasuan ### 前期准备 下载并注册nuget帐号 下载地址 https:// ...

  6. Java基础(39)Arrays.binarySearch方法

    1.源码中可以看到,binarySearch方法调用了binarySearch0方法,binarySearch0方法才是标准的二分查找实现. 2.对于binarySearch0方法来说,注意最后的re ...

  7. C# .NET .NET Framework .NET CORE 等的关系简介

    2019新的一年,祝大家新年快乐,工作生活一帆风顺,心想事成!诸事大吉! 这篇文章是我今年的第一篇博客,主题是:C#  .NET  .NET Framework   .NET CORE  等这些名词之 ...

  8. 设计模式C++描述----22.访问者(Visitor)模式

    一. 访问者模式 定义:表示一个作用于某对象结构中的各元素的操作.它你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 结构如下: 二. 举例 假设有一项科学实验,是用来对比两种种子在不同环 ...

  9. 数字麦克风PDM信号采集与STM32 I2S接口应用(三)

    本文是数字麦克风笔记文章的数据处理篇. 读取数字麦克风的信号,需要嵌入式驱动和PC应用的结合,驱动负责信号采集,应用代码负责声音分析. 一般而言,在完成特征分析和实验之后,把优化过的代码固化到嵌入式端 ...

  10. Java 方法重载 (Overload)

    对重载 (Overload) 的认识 为什么要用方法重载: 对于功能类似的方法来说,因为参数列表不一样,如果定义不同名称的方法,太麻烦且难以记忆. 为了解决这个问题,引入方法的重载. 重载的定义: 多 ...