首页 文章 精选 留言 我的

精选列表

搜索[特殊字符],共10011篇文章
优秀的个人博客,低调大师

JAVA之带转义字符的json字符串解析

Json数据: { "ret":"0", "Weathers":"[{\"date\":\"2017-03-20\",\"weatherType\":\"阴,大部分地区有零星小雨或小雨,上午能见度2-5km转阴天间多云\",\"temperature_min\":\"6\",\"wind_force\":\"微风\",\"temperature_max\":\"12\",\"wind_direction\":\"\"},{\"date\":\"2017-03-21\",\"weatherType\":\"阴转多云\",\"temperature_min\":\"6\",\"wind_force\":\"微风\",\"temperature_max\":\"13\",\"wind_direction\":\"\"},{\"date\":\"2017-03-22\",\"weatherType\":\"多云转阴\",\"temperature_min\":\"6\",\"wind_force\":\"微风\",\"temperature_max\":\"14\",\"wind_direction\":\"\"},{\"date\":\"2017-03-23\",\"weatherType\":\"阴转小雨\",\"temperature_min\":\"3\",\"wind_force\":\"微风\",\"temperature_max\":\"10\",\"wind_direction\":\"\"},{\"date\":\"2017-03-24\",\"weatherType\":\"雨夹雪转多云\",\"temperature_min\":\"0\",\"wind_force\":\"微风\",\"temperature_max\":\"5\",\"wind_direction\":\"\"},{\"date\":\"2017-03-25\",\"weatherType\":\"多云转晴\",\"temperature_min\":\"5\",\"wind_force\":\"微风转3到4级\",\"temperature_max\":\"13\",\"wind_direction\":\"\"},{\"date\":\"2017-03-26\",\"weatherType\":\"晴\",\"temperature_min\":\"4\",\"wind_force\":\"3到4级\",\"temperature_max\":\"14\",\"wind_direction\":\"\"}]" } JSONObjectjsonObject=newJSONObject(result); Stringweathers=jsonObject.getString("Weathers"); JSONArrayweathersArray=newJSONArray(weathers); //直接这样解析会报错 //JSONArrayweathersArray=jsonObject.getJSONArray("weathers"); 转载于:http://blog.csdn.net/panyzyw/article/details/64130020

优秀的个人博客,低调大师

[Android Pro] ListView,GridView之LayoutAnimation特殊动画的实现

转载自:http://gundumw100.iteye.com/blog/1874545 LayoutAnimation干嘛用的?不知道的话网上搜一下。Android的Animation之LayoutAnimation使用方法 有两种用法,我的通常写在代码中,像下面这样: /** * Layout动画 * * @return */ protected LayoutAnimationController getAnimationController() { int duration=300; AnimationSet set = new AnimationSet(true); Animation animation = new AlphaAnimation(0.0f, 1.0f); animation.setDuration(duration); set.addAnimation(animation); animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f, Animation.RELATIVE_TO_SELF, 0.0f); animation.setDuration(duration); set.addAnimation(animation); LayoutAnimationController controller = new LayoutAnimationController(set, 0.5f); controller.setOrder(LayoutAnimationController.ORDER_NORMAL); return controller; } 应用的时候只需这样: listView = (ListView) findViewById(R.id.listView); listView.setLayoutAnimation(getAnimationController()); adapter = new ListViewAdapter(stores); listView.setAdapter(adapter); 这样一个简单的LayoutAnimation就完成了。 别看到这里就以为文章就完了,以上都是些小玩意。呵呵,还有更厉害的! 你想设置更炫的动画吗?LayoutAnimation通常是Item一个一个的出现,有某种规律的。想让每个Item都有自己的动画吗?那就继续看下去。 ....... private int duration=1000; private Animation push_left_in,push_right_in; private Animation slide_top_to_bottom,slide_bottom_to_top; public ListViewAdapter(ArrayList<Store> list) { this.list = list; push_left_in=AnimationUtils.loadAnimation(context, R.anim.push_left_in); push_right_in=AnimationUtils.loadAnimation(context, R.anim.push_right_in); slide_top_to_bottom=AnimationUtils.loadAnimation(context, R.anim.slide_top_to_bottom); slide_bottom_to_top=AnimationUtils.loadAnimation(context, R.anim.slide_bottom_to_top); } ........ @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub ViewHodler hodler; if (convertView == null) { hodler = new ViewHodler(); convertView = LayoutInflater.from(context).inflate( R.layout.simple_item_7_for_main, null); ........ convertView.setTag(hodler); if (position % 2 == 0) { push_left_in.setDuration(duration); convertView.setAnimation(push_left_in); } else { push_right_in.setDuration(duration); convertView.setAnimation(push_right_in); } /*if(position==0){ slide_bottom_to_top.setDuration(duration); convertView.setAnimation(slide_bottom_to_top); } else{ slide_top_to_bottom.setDuration(duration); convertView.setAnimation(slide_top_to_bottom); }*/ }else{ hodler = (ViewHodler) convertView.getTag(); } ........ return convertView; } 看见上面的动画设置了吗?将动画写在getView()中,这样可以设置很多不同的动画。其实这不属于LayoutAnimation的范畴了。 你可以试一下,如果设计好的话,可以有比LayoutAnimation更酷的效果。 我这里只试了两种效果。 下面是我的动画文件,共四个: 第一种效果:item分别从左右两侧滑入效果。 push_left_in.xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="-100%p" android:toXDelta="0" android:duration="300"/> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> </set> push_right_in.xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="100%p" android:toXDelta="0" android:duration="300"/> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> </set> 第2种效果:第一个item从下往上滑入,其他Item从上往下滑入效果,这个效果如果单个Item比较高(height)的话效果非常酷(卡牛的老版本好像用的就是这种效果)。 slide_bottom_to_top.xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> <translate android:fromYDelta="100%" android:toXDelta="0" android:duration="300" /> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> </set> slide_top_to_bottom.xml <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> <translate android:fromYDelta="-100%" android:toXDelta="0" android:duration="300" /> <alpha android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="300" /> </set> 另外一篇: 这个不是我写的。GridView的item从上下左右飞入 import java.util.Random; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.GridView; import android.widget.ImageView; public class ZdemoActivity extends Activity { private GridView gv; private Button btn; private TranslateAnimation taLeft, taRight, taTop, taBlow; private int[] imgList = new int[15]; private MyAdapter mAdapter; private LayoutInflater mInflater; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.InitView(); this.InitAnima(); this.InitData(); } private void InitAnima() { // TODO Auto-generated method stub taLeft = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f); taRight = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f); taTop = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 1.0f, Animation.RELATIVE_TO_PARENT, 0.0f); taBlow = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f); taLeft.setDuration(1000); taRight.setDuration(1000); taTop.setDuration(1000); taBlow.setDuration(1000); } private void InitData() { // TODO Auto-generated method stub for (int i = 0; i < 15; i++) { imgList[i] = R.drawable.ic_launcher; } mAdapter = new MyAdapter(); gv.setAdapter(mAdapter); } private void InitView() { // TODO Auto-generated method stub gv = (GridView) findViewById(R.id.gridView1); btn = (Button) findViewById(R.id.button1); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub mAdapter = null; mAdapter = new MyAdapter(); gv.setAdapter(mAdapter); mAdapter.notifyDataSetChanged(); } }); mInflater = (LayoutInflater) this .getSystemService(Context.LAYOUT_INFLATER_SERVICE); } private class MyAdapter extends BaseAdapter { @Override public int getCount() { // TODO Auto-generated method stub return imgList.length; } @Override public Object getItem(int position) { // TODO Auto-generated method stub return imgList[position]; } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub ViewHolder holder = null; if (convertView == null) { convertView = mInflater.inflate(R.layout.item, null); holder = new ViewHolder(); holder.image = (ImageView) convertView .findViewById(R.id.imageView1); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } int imgID = imgList[position]; holder.image.setImageResource(imgID); Random ran = new Random(); int rand = ran.nextInt(4); switch (rand) { case 0: convertView.startAnimation(taLeft); break; case 1: convertView.startAnimation(taRight); break; case 2: convertView.startAnimation(taTop); break; case 3: convertView.startAnimation(taBlow); break; } return convertView; } class ViewHolder { public ImageView image; } } } 分类: Android Pro 本文转自demoblog博客园博客,原文链接http://www.cnblogs.com/0616--ataozhijia/p/3907322.html如需转载请自行联系原作者 demoblog

优秀的个人博客,低调大师

移动html特殊链接【打电话_发短信_发邮件】

如果需要在移动浏览器中实现拨打电话,调用sms,发送email等功能,移动手机WEB页面(HTML5)Javascript提供的接口是一个好办法。 采用url链接的方式,实现在SafariiOS,Android浏览器,webos浏览器,塞班浏览器,IE,Operamini等主流浏览器,进行拨打电话功能。 1. 打电话 在电话号码前面可以加上 + (加号)表示国际号码。如: 最常用WEB页面JS实现一键拨号的电话拨打功能 例子: <ahref="tel:10086">10086</a> 使用wtai协议进行拨打电话 <ahref="wtai://wp/mc;10086">10086</a> 2. 短信 如果是需要调用短信的接口,可以将链接写成下面的格式: sms:<phone_number>[,<phone-number>]*[?body=<message_body>] 例如: <ahref="sms:10086">给10086发短信</a><br/> <ahref="sms:10086?body=cxye">给10086发送内容为"cxye"的短信</a><br/> <ahref="sms:10086,10010?body=cxye">给10086和10010发送内容为"cxye"的短信</a> 3. Mail 邮件 就和普通的html一样使用mailto <ahref="mailto:test1@163.com">mail</a> <ahref="mailto:test1@163.com,test2@126.com">mail</a> <ahref="mailto:test1@163.com?subject=Testing">mail</a> <ahref="mailto:test1@163.com?subject=Testingmailto&cc=test3@126.com">mail</a> 4. Android Market 如果希望一个链接能够激活Android市场的功能,可以把链接写成: <ahref="market://search?q=[query]">AndroidMarketlink</a> 其中<query>就是搜索的内容,你应用的名称 例子: <ahref="market://search?q=MyApp">MyApp</a> 5. 地图定位GPS <ahref="geopoint:[经度],[纬度]">我的位置</a> 例如: <ahref="geopoint:108.954823,34.275891">我的位置</a> 本文转自程序猿博客51CTO博客,原文链接http://blog.51cto.com/haihuiwei/1933348如需转载请自行联系原作者 365850153

优秀的个人博客,低调大师

阿里云SLB健康检查的日志特殊处理

SLB健康检查的频率比较高,每秒都有好几个。日积月累就带来了大量web service的日志。一来浪费存储空间,二来消耗磁盘性能。除了官方建议的降低健康检查频率外,或许有一些变通的思维 1、如果对HTTP或者TCP转发协议不挑剔,换成TCP转发并不开启http健康检查就OK了。 优势:方便,控制台操作下就好了。 劣势:只是这样也失去了应用层异常的发现能力。 2、为健康检查单独建立一个没有开启日志的virtualhost,通过不同的端口或者hostname,让健康检查请求不会请求到业务host。 优势:web service级别的异常依然能够被健康检查发现。 劣势:virtualhost级别的异常,SLB健康检查就无法发现了。 3、利用nginx等web服务器的规则,为健康检查的文件名(URI)做单独的日志配置,以Nginx为例: 如下配置,健康检查配置一个业务上无意义的check.html。针对这个文件做的请求,不记录日志。 1 2 3 4 5 location ~ / check .html { access_log off ; #关闭日志 #access_log /var/log/nginx/access/health_check.log main;或者健康检查的日志配置到单独的文件 } 优势:健康检查日志可以随意配置,且不会损失健康检查的特性。

优秀的个人博客,低调大师

Java 字符串 之 字符串 记事本实例

http://www.verejava.com/?id=17159658585531 import java.util.Scanner; public class Test { private Scanner in; private StringBuffer sb = new StringBuffer(); private boolean flag = true; private String clipBoard;//剪贴板 public Test() { in = new Scanner(System.in); System.out.println("1 : 键盘输入编辑文字"); System.out.println("2 : 追加文字"); System.out.println("3 : 插入文字 (索引号:要插入的文字)"); System.out.println("4 : 替换文字 (要替换的文字:新文字)"); System.out.println("5 : 查找文字(返回找到的文字的索引号)"); System.out.println("6 : 删除文字(开始索引号:结束索引号)"); System.out.println("7 : 复制文字 (开始索引号:结束索引号)"); System.out.println("8 : 粘贴文字(索引号)"); System.out.println("9 : 剪切文字(开始索引号:结束索引号)"); System.out.println("10 : 转换成大写"); System.out.println("11 : 转换成小写"); System.out.println("12 : 文字反转"); System.out.println("-1 : 退出编辑"); while (flag) { String key = in.nextLine(); if ("1".equals(key)) { input(); } if ("-1".equals(key)) { exit(); } if ("2".equals(key)) { append(); } if ("3".equals(key)) { insert(); } if ("4".equals(key)) { replace(); } if ("5".equals(key)) { search(); } if ("6".equals(key)) { delete(); } if ("7".equals(key)) { copy(); } if ("8".equals(key)) { paste(); } if ("9".equals(key)) { cut(); } if ("10".equals(key)) { toUpperCase(); } if ("11".equals(key)) { toLowerCase(); } if ("12".equals(key)) { rerverse(); } System.out.println("记事本当前文本:" + sb.toString()); } } private void rerverse() { sb.reverse(); } private void toLowerCase() { String buffer = sb.toString(); buffer = buffer.toLowerCase(); sb.delete(0, sb.length()); sb.append(buffer); } private void toUpperCase() { String buffer = sb.toString(); buffer = buffer.toUpperCase(); sb.delete(0, sb.length()); sb.append(buffer); } private void cut() { System.out.println("请输入要剪切文字(开始索引号:结束索引号)"); String text = in.nextLine(); String[] texts = text.split(":"); int startIndex = Integer.parseInt(texts[0]); int endIndex = Integer.parseInt(texts[1]); clipBoard = sb.substring(startIndex, endIndex); //删除记事本文字 sb.delete(startIndex, endIndex); } private void paste() { System.out.println("请输入要粘贴文字(索引号)"); String text = in.nextLine(); int index = Integer.parseInt(text); //粘贴 sb.insert(index, clipBoard); } private void copy() { System.out.println("请输入要复制文字 (开始索引号:结束索引号)"); String text = in.nextLine(); String[] texts = text.split(":"); int startIndex = Integer.parseInt(texts[0]); int endIndex = Integer.parseInt(texts[1]); clipBoard = sb.substring(startIndex, endIndex); } private void delete() { System.out.println("请输入要删除文字(开始索引号:结束索引号)"); String text = in.nextLine(); String[] texts = text.split(":"); int startIndex = Integer.parseInt(texts[0]); int endIndex = Integer.parseInt(texts[1]); //删除 sb.delete(startIndex, endIndex); } private void search() { System.out.println("请输入要查找文字(返回找到的文字的索引号)"); String text = in.nextLine(); int index = sb.indexOf(text); if (index >= 0) { System.out.println(text + " 索引号:" + index); } else { System.out.println(text + " 不存在"); } } private void replace() { System.out.println("请输入要替换文字 (要替换的文字:新文字)"); String text = in.nextLine(); String[] texts = text.split(":"); String oldText = texts[0]; String newText = texts[1]; //替换 String buffer = sb.toString(); buffer = buffer.replace(oldText, newText); sb.delete(0, sb.length());//清空记事本文字 sb.append(buffer); } private void insert() { System.out.println("请输入要插入的文字: (索引号:要插入的文字)"); String text = in.nextLine(); String[] texts = text.split(":"); int index = Integer.parseInt(texts[0]); String str = texts[1]; //插入文字 sb.insert(index, str); } private void append() { System.out.println("请输入要追加文字:"); String text = in.nextLine(); //追加文字 sb.append(text); } private void exit() { flag = false; System.exit(0);//退出应用程序 } private void input() { System.out.println("请输入要编辑的文字:"); String text = in.nextLine(); //添加到 sb 记事本的缓冲区 sb.append(text); } public static void main(String[] args) { new Test(); } } http://www.verejava.com/?id=17159658585531

优秀的个人博客,低调大师

OceanBase 特殊的 INT 与时间类型隐式转换问题

本文作者分享了Oceanbase时间与数值类型隐式转换导致查询结果不符合预期或“不正确”问题的排查思路。 作者:任仲禹 爱可生 DBA 团队成员,擅长故障分析和性能优化,文章相关技术问题,欢迎大家一起讨论。 本文来源:原创投稿 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。 之前在 OceanBase 使用中碰到了一个“令人费解”的数据类型隐式转换问题。结论比较简单,特跟大家分享下排查思路。 问题描述 某客户项目组执行更新 SQL 语句时会偶发失败,报错如下: 脱敏处理后 ERROR bad SQL grammar [update renzy set at=current_timestamp,expire_at=(cast(unix_timestamp(current_timestamp(3) as unsigned) +?)), order_id= ? where id = ? and (expire_at < current_timestamp or order_id = ?)] java.sql.SQLSyntaxErrorException: (conn=1277168) Incorrect value. 查询 OceanBase 版本。 ./observer -V observer (OceanBase 3.2.3.2) REVISION: 105000092022092216-445151f0edb502e00ae5839dfd92627816b2b822 查看表结构和数据。 MySQL [test]> show create table renzy\G *************************** 1. row *************************** Table: renzy Create Table: CREATE TABLE `renzy` ( `id` varchar(64) NOT NULL, `at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `order_id` varchar(64) NOT NULL, `expire_at` bigint(20) NOT NULL, `vt` timestamp NOT NULL, PRIMARY KEY (`id`) ) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 3 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 MySQL [test]> select * from renzy; +----+---------------------------+---------------+------------+---------------------------------+ | id | at | order_id | expire_at | vt | +----+---------------------------+---------------+------------+---------------------------------+ | 1 | 2023-07-07 14:57:13 | 0:16632@172.1 | 1716040750 | 2023-07-07 14:57:13 | +----+---------------------------+---------------+------------+---------------------------------+ 1 row in set (0.02 sec) 问题排查 问题 1:报错语句 直接执行报错的 SQL 语句。 update renzy set at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:16632@172.24.64.1' where id = '1' and (expire_at < CURRENT_TIMESTAMP or order_id = '0:16632@172.24.64.1') ERROR 1292 (22007): Incorrect values. 问题 2 但是,当主键 ID 匹配不到任何数据,UPDATE 语句将不会报错。 为啥?先记录下问题接着看日志。 # 表不存在 id=2 的数据 update renzy set acquired_at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:16632@172.24.64.1' where id = '2' and (expire_at < CURRENT_TIMESTAMP or order_id = '0:16632@172.24.64.1') Query OK. 报错 SQL 的 sql_auit 输出和日志 带着问题分析下 sql$gv_audit 输出,得出如下信息: 该语句计划是本地计划,分发到了 0.71 节点。 错误码是 4219 (无效的 datetime 值)。 MySQL [oceanbase]> select trace_id,svr_ip,ret_code,retry_cnt,usec_to_time(request_time),elapsed_time,execute_time,plan_type,query_sql from gv$sql_audit where query_sql like 'update id%' and ret_code != 0 order by request_time desc limit 5; +-----------------------------------+-------------+----------+-----------+----------------------------+--------------+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | trace_id | svr_ip | ret_code | retry_cnt | usec_to_time(request_time) | elapsed_time | execute_time | plan_type | query_sql | +-----------------------------------+-------------+----------+-----------+----------------------------+--------------+--------------+-----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | YB420CF10047-0005FBCCEF6E3635-0-0 | 12.241.0.71 | -4219 | 0 | 2023-06-06 15:32:08.375051 | 689 | 611 | 1 | update id set acquired_at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:16632@172.24.64.1' where id = '2' and (expire_at < CURRENT_TIMESTAMP or order_id = '0:16632@172.24.64.1') | ····· 5 rows in set (5.21 sec) 关键报错日志 0.72 的 obs.log。 #grep YB420CF10047-0005FBCCEF6E3635-0-0 observer.log.20230606153309 [2023-06-06 15:32:08.375202] WARN [LIB.TIME] int_to_ob_time_with_date (ob_time_convert.cpp:1618) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=5] [dc=0] datetime is invalid or out of range(ret=-4219, int64=0) [2023-06-06 15:32:08.375211] WARN [LIB.TIME] int_to_datetime (ob_time_convert.cpp:329) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=8] [dc=0] failed to convert integer to datetime(ret=-4219) [2023-06-06 15:32:08.375214] WARN [SQL] common_int_datetime (ob_datum_cast.cpp:709) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=3] [dc=0] int_datetime failed(ret=-4219) [2023-06-06 15:32:08.375219] WARN [SQL] int_datetime (ob_datum_cast.cpp:2076) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=4] [dc=0] fail to exec common_int_datetime(expr, in_val, ctx, res_datum)(ret=-4219, in_val=1716036728) [2023-06-06 15:32:08.375223] WARN [SQL] get_comparator_operands (ob_expr_operator.h:1131) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=4] [dc=0] left eval failed(ret=-4219) [2023-06-06 15:32:08.375227] WARN [SQL.ENG] def_relational_eval_func (ob_expr_cmp_func.cpp:54) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=4] [dc=0] failed to eval args(ret=-4219) [2023-06-06 15:32:08.375232] WARN [SQL.ENG] calc_or_exprN (ob_expr_or.cpp:221) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=5] [dc=0] eval arg 0 failed(ret=-4219) [2023-06-06 15:32:08.375240] WARN [SQL.ENG] filter_row (ob_operator.cpp:915) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=7] [dc=0] expr evaluate failed(ret=-4219, expr=0x7f37808ec058) [2023-06-06 15:32:08.375247] WARN [STORAGE] check_filtered (ob_multiple_merge.cpp:1249) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=7] [dc=0] filter row failed(ret=-4219) [2023-06-06 15:32:08.375251] WARN [STORAGE] process_fuse_row (ob_multiple_merge.cpp:787) [38763][0][YB420CF10047-0005FBCCEF6E3635-0-0] [lt=3] [dc=0] fail to check row filtered(ret=-4219) 观察到最开始抛出错误的是 int_to_ob_time_with_date 方法,报的错是 “datetime is invalid or out of range(ret=-4219, int64=0)”,即值无效或超过范围。 这里 UPDATE 的调用链路是: common_int_datetime -> int_to_datetime -> int_to_ob_time_with_date 问题 3:尝试使用 SELECT 查询结果 表中 EXPIRE_AT 存储的是未来时间(1716040750),与当前时间(1686042749)做比较,查询结果理应不输出结果才对?(先记录为问题 3:查询结果不符合预期) # 表中只有 1 行记录,且 EXPIRE_AT 的值为 1716040750 MySQL [mock_db]> select * from renzy where EXPIRE_AT < CURRENT_TIMESTAMP; +-------+---------------------+---------------------+------------+------------------------------------+ | id | ACQUIRED_AT | order_id | EXPIRE_AT | LST_UP_TM | +-------+---------------------+---------------------+------------+------------------------------------+ | 1 | 2023-06-06 16:39:10 | 0:16632@172.24.64.1 | 1716040750 | 2023-06-06 16:39:10 | +-------+---------------------+---------------------+------------+------------------------------------+ 1 row in set (0.05 sec) # 当前时间戳 MySQL [mock_db]> select unix_timestamp(CURRENT_TIMESTAMP); +--------------------------------------------+ | unix_timestamp(CURRENT_TIMESTAMP) | +--------------------------------------------+ | 1686042749 | +--------------------------------------------+ 1 row in set (0.03 sec) SELECT 查询的报错日志。 [2023-06-06 17:08:54.307371] WARN [LIB.TIME] int_to_ob_time_with_date (ob_time_convert.cpp:1618) [38763][0][YB420CF10047-0005FBCCEF6F9E6D-0-0] [lt=10] [dc=0] datetime is invalid or out of range(ret=-4219, int64=0) [2023-06-06 17:08:54.307382] WARN [LIB.TIME] int_to_datetime (ob_time_convert.cpp:329) [38763][0][YB420CF10047-0005FBCCEF6F9E6D-0-0] [lt=10] [dc=0] failed to convert integer to datetime(ret=-4219) 这里 SELECT 的调用链路:int_to_datetime -> int_to_ob_time_with_date 以上就是存疑的几个问题,那在具体分析前,先了解一下前置知识点:OceanBase 的隐式转换。 OceanBase 的隐式转换 数据类型 bigint 与 datetime 的值是没法直接比较,需要先将 int 转换为时间类型,这就是所谓的隐式转换,所以这里 OceanBase 是如何转很重要。 int 类型转换成 OceanBase 认可的时间类型(即 OBTime)并不是用的 from_unixtime 这个函数,而是用的 OceanBase 自己内部的逻辑。 源码中涉及 int,double,string 类型隐式转换的逻辑如下: int_to_datetime //////////////////////////////// // int / double / string -> datetime / date / time / year. int ObTimeConverter::int_to_datetime(int64_t int_part, int64_t dec_part, const ObTimeConvertCtx &cvrt_ctx, int64_t &value, const ObDateSqlMode date_sql_mode) { int ret = OB_SUCCESS; dec_part = (dec_part + 500) / 1000; if (0 == int_part) { value = ZERO_DATETIME; } else { ObTime ob_time(DT_TYPE_DATETIME); ObDateSqlMode local_date_sql_mode = date_sql_mode; if (cvrt_ctx.is_timestamp_) { local_date_sql_mode.allow_invalid_dates_ = false; } if (OB_FAIL(int_to_ob_time_with_date(int_part, ob_time, false, local_date_sql_mode))) { LOG_WARN("failed to convert integer to datetime", K(ret)); } else if (OB_FAIL(ob_time_to_datetime(ob_time, cvrt_ctx, value))) { LOG_WARN("failed to convert datetime to seconds", K(ret)); } } value += dec_part; if (OB_SUCC(ret) && !is_valid_datetime(value)) { ret = OB_DATETIME_FUNCTION_OVERFLOW; LOG_WARN("datetime filed overflow", K(ret), K(value)); } return ret; } 最终调用的 int_to_ob_time_with_date: //////////////////////////////// // int / uint / string -> ObTime / ObInterval <- datetime / date / time. int ObTimeConverter::int_to_ob_time_with_date(int64_t int64, ObTime &ob_time, bool is_dayofmonth, const ObDateSqlMode date_sql_mode) { int ret = OB_SUCCESS; int32_t *parts = ob_time.parts_; if (is_dayofmonth && 0 == int64) { parts[DT_SEC] = 0; parts[DT_MIN] = 0; parts[DT_HOUR] = 0; parts[DT_MDAY] = 0; parts[DT_MON] = 0; parts[DT_YEAR] = 0; } else if (int64 < power_of_10[2]) { ret = OB_INVALID_DATE_VALUE; LOG_WARN("datetime integer is out of range", K(ret), K(int64)); } else if (int64 < power_of_10[8]) { // YYYYMMDD. parts[DT_MDAY] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2]; parts[DT_MON] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2]; parts[DT_YEAR] = static_cast<int32_t>(int64 % power_of_10[4]); } else if (int64 / power_of_10[6] < power_of_10[8]) { // YYYYMMDDHHMMSS. parts[DT_SEC] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2]; parts[DT_MIN] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2]; parts[DT_HOUR] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2]; parts[DT_MDAY] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2]; parts[DT_MON] = static_cast<int32_t>(int64 % power_of_10[2]); int64 /= power_of_10[2]; parts[DT_YEAR] = static_cast<int32_t>(int64 % power_of_10[4]); } else { ret = OB_INVALID_DATE_VALUE; LOG_WARN("datetime integer is out of range", K(ret), K(int64)); } if (OB_SUCC(ret)) { apply_date_year2_rule(parts[0]); if (OB_FAIL(validate_datetime(ob_time, is_dayofmonth, date_sql_mode))) { LOG_WARN("datetime is invalid or out of range", K(ret), K(int64)); } else if (ZERO_DATE != parts[DT_DATE]) { parts[DT_DATE] = ob_time_to_date(ob_time); } } return ret; } 上面代码表示 OceanBase 仅能识别的格式: YYYYMMDD YYYYMMDDHHMMSS 一旦不是上述格式就会抛出报错。下图可以看到只有 20230816 和 20230819111111 两种符合条件。 问题原因 问题 3:SELECT 查出的结果不符合预期 bigint 与 datetime 类型"比较"涉及隐式转换导致结果不可预知。 select * from renzy where EXPIRE_AT < CURRENT_TIMESTAMP; 中 EXPIRE_AT 的值是 bigint 类型,且值为 1716040750。 值 1716040750 无法匹配 int_to_ob_time_with_date 规定的格式,将抛出告警“datetime integer is out of range”,日志中也能印证这一点。 这里按理应该在 SQL 执行时抛出报错,不应该输出结果的,但是为了 OB 兼容 MySQL 而选择输出了“错误”的值。 为啥 MySQL 不会报错? MySQL 中转换失败之后,其实会有默认值 0,上述 OB 行为转换失败用的默认值,应该和 MySQL 兼容,所以满足了 WHERE 条件 0 < 1686042749,SELECT 就输出了结果。 问题 1:UPDATE 语句为何能吐出报错 因为 OB 默认开启了 SQL_MODE 严格模式,如果发生隐式转换且转换失败(用了默认值)的场景,OB 的严格模式比 MySQL 做了一层防范,将禁止 SQL 执行。 MySQL 开启 SQL_MODE 严格模式下能执行“成功”。 问题 2:UPDATE 语句 WHERE 条件中主键匹配到不存在的值不报错 UPDATE 走的是 table get 算子,等值查询不到结果后,不需要再 filter 后面的条件。这解释了项目组偶发失败的问题。 最后一个问题:为啥 UPDATE 直接报错,SELECT 确能查出“错误的值” 此报错仅抛出报错到 obs.log。 我这边猜测是因为只在 UPDATE 的时候会遵循严格模式,而 SELECT 则不需要遵循,依据前文提到的调用链路: UPDATE: common_int_datetime -> int_to_datetime -> int_to_ob_time_with_date SELECT: int_to_datetime -> int_to_ob_time_with_date UPDATE 多了上层的 common_int_datetime,调用入口不一样,而根据这个方法,大致猜测是因为 SQL_MODE 的严格模式导致。 验证下猜想,看起来清空 SQL_MODE 后,UPDATE 果然能插入成功。 MySQL [test]> select @@sql_mode; +--------------------------------------------+ | @@sql_mode | +--------------------------------------------+ | STRICT_ALL_TABLES,NO_ZERO_IN_DATE | +--------------------------------------------+ 1 row in set (0.01 sec) MySQL [test]> update renzy set at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:16632@172.24.64.1' where id = '1' and (expire_at < CURRENT_TIMESTAMP or order_id = '0:16632@172.24.64.1'); ERROR 1292 (22007): Incorrect value MySQL [test]> set sql_mode=''; Query OK, 0 rows affected (0.01 sec) MySQL [test]> update renzy set at=CURRENT_TIMESTAMP, expire_at=(cast(unix_timestamp(current_timestamp(3)) as unsigned) + 30000000), order_id= '0:16632@172.24.64.1' where id = '1' and (expire_at < CURRENT_TIMESTAMP or order_id = '0:16632@172.24.64.1'); Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 可能我的猜测有问题,但个人能力有限,欢迎大家一起指正和讨论。 结论 让客户项目组改写 SQL 逻辑。 EXPIRE_AT < CURRENT_TIMESTAMP 改成 EXPIRE_AT < unix_timestamp(CURRENT_TIMESTAMP) 强调 SQL 规范,避免隐式转换。 关于 SQLE 爱可生开源社区的 SQLE 是一款面向数据库使用者和管理者,支持多场景审核,支持标准化上线流程,原生支持 MySQL 审核且数据库类型可扩展的 SQL 审核工具。 SQLE 获取 类型 地址 版本库 https://github.com/actiontech/sqle 文档 https://actiontech.github.io/sqle-docs/ 发布信息 https://github.com/actiontech/sqle/releases 数据审核插件开发文档 https://actiontech.github.io/sqle-docs-cn/3.modules/3.7_auditplugin/auditplugin_development.html

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册